[Travelr-commits] r9 - in pre-alpha: . travelr travelr/R travelr/data travelr/man travelr/src travelr/tests travelr/tests/Network-Source

noreply at r-forge.r-project.org noreply at r-forge.r-project.org
Mon Apr 26 04:15:29 CEST 2010


Author: jrawbits
Date: 2010-04-26 04:15:14 +0200 (Mon, 26 Apr 2010)
New Revision: 9

Added:
   pre-alpha/travelr/
   pre-alpha/travelr/DESCRIPTION
   pre-alpha/travelr/NAMESPACE
   pre-alpha/travelr/R/
   pre-alpha/travelr/R/Assignment_Set.R
   pre-alpha/travelr/R/Highway_Assignment.R
   pre-alpha/travelr/R/Highway_Network.R
   pre-alpha/travelr/R/Iterative_Fitting.R
   pre-alpha/travelr/R/Path_Functions.R
   pre-alpha/travelr/R/tntp.R
   pre-alpha/travelr/R/zzz.R
   pre-alpha/travelr/data/
   pre-alpha/travelr/data/SiouxFalls.rda
   pre-alpha/travelr/data/datalist
   pre-alpha/travelr/man/
   pre-alpha/travelr/man/assignment.functions.Rd
   pre-alpha/travelr/man/assignment.set.Rd
   pre-alpha/travelr/man/build.intercepts.Rd
   pre-alpha/travelr/man/build.network.set.Rd
   pre-alpha/travelr/man/build.paths.Rd
   pre-alpha/travelr/man/cost.functions.Rd
   pre-alpha/travelr/man/highway.assign.Rd
   pre-alpha/travelr/man/highway.net.Rd
   pre-alpha/travelr/man/intercept.set.Rd
   pre-alpha/travelr/man/ipf.Rd
   pre-alpha/travelr/man/node.map.Rd
   pre-alpha/travelr/man/package.Rd
   pre-alpha/travelr/man/sample.networks.Rd
   pre-alpha/travelr/man/shortest.paths.Rd
   pre-alpha/travelr/man/tntp.Rd
   pre-alpha/travelr/man/utilities.Rd
   pre-alpha/travelr/plan.txt
   pre-alpha/travelr/src/
   pre-alpha/travelr/src/build_path_R.cc
   pre-alpha/travelr/src/build_path_R.h
   pre-alpha/travelr/src/build_r_shlib.bat
   pre-alpha/travelr/src/elements_R.h
   pre-alpha/travelr/src/makevars.win
   pre-alpha/travelr/src/path_templates_R.h
   pre-alpha/travelr/src/short_path_R.cc
   pre-alpha/travelr/tests/
   pre-alpha/travelr/tests/Network-Source/
   pre-alpha/travelr/tests/Network-Source/Richmond.VA_Meta.txt
   pre-alpha/travelr/tests/Network-Source/Richmond.VA_Network.csv
   pre-alpha/travelr/tests/Network-Source/Richmond.VA_Nodes.csv
   pre-alpha/travelr/tests/Network-Source/Richmond.VA_OD.csv
   pre-alpha/travelr/tests/Network-Source/Richmond.VA_Penalties.txt
   pre-alpha/travelr/tests/Network-Source/SiouxFalls-Assignment.s
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_Cube.net
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_CubeLoaded.net
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_LoadTarget.dbf
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_Meta.txt
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_Network.csv
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_Nodes.csv
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_OD.csv
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_OD.mat
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_OD_Cube.csv
   pre-alpha/travelr/tests/Network-Source/SiouxFalls_PathTrace.csv
   pre-alpha/travelr/tests/RTC_Links.csv
   pre-alpha/travelr/tests/RTC_Network.RData
   pre-alpha/travelr/tests/RTC_Nodes.csv
   pre-alpha/travelr/tests/RTC_OD.csv
   pre-alpha/travelr/tests/RTC_Turn_Penalties.txt
   pre-alpha/travelr/tests/Test_01_Highway_Network.R
   pre-alpha/travelr/tests/Test_02_Assignment_Classes.R
   pre-alpha/travelr/tests/Test_03_Highway_Assignment.R
   pre-alpha/travelr/tests/Test_04_ipf.R
   pre-alpha/travelr/tests/data/
   pre-alpha/travelr/tests/install.travelr.bat
   pre-alpha/travelr/tests/run.tests.bat
   pre-alpha/travelr_0.1-1.zip
Log:
The initial pre-alpha release of TravelR.  There's a bunch that probably doesn't work (especially, for example, the skim.paths function), the documentation sucks, and I haven't tested the latest round of multi-class assignment code with more than class, nor have I tested the MSA algorithm, nor have I copied the Frank-Wolfe algorithm into this tree and set it up for multi-class and intercepts.  Whew!  That said, it's amazingly close to done, at least until we look at the performance on a big network and decide that more needs to be moved to C++!

Added: pre-alpha/travelr/DESCRIPTION
===================================================================
--- pre-alpha/travelr/DESCRIPTION	                        (rev 0)
+++ pre-alpha/travelr/DESCRIPTION	2010-04-26 02:15:14 UTC (rev 9)
@@ -0,0 +1,16 @@
+Package: travelr
+Version: 0.1-1
+Date: 2009-12-09
+Title: Functions for Travel Demand Modeling
+Author: Jeremy Raw
+Maintainer: Jeremy Raw <jeremy.raw at earthlink.net>
+Depends: R (>= 2.10.0)
+Suggests: igraph, sp
+Description: Provides a suite of functions for building travel demand models, with C/C++ support.
+ Includes fast routines for common operations that are specific to travel demand modeling including
+ gravity model, iterative proportional fitting (Fratar/Furness), network path building, and highway
+ assignment (several common and contemporary equilibrium assignment algorithms are implemented).
+Classification/JEL: R.41
+License: GPL (>= 2)
+LazyData: yes
+URL: http://www.opentravelmodels.org

Added: pre-alpha/travelr/NAMESPACE
===================================================================
--- pre-alpha/travelr/NAMESPACE	                        (rev 0)
+++ pre-alpha/travelr/NAMESPACE	2010-04-26 02:15:14 UTC (rev 9)
@@ -0,0 +1,67 @@
+# travelr namespace
+
+# DLL load instruction
+useDynLib(travelr,
+          shortest_paths,
+          load_paths,
+          build_and_load_paths,
+          skim_paths,
+          walk_paths,
+          walk_pairs,
+          intercept_paths)
+
+# Highway Network functions
+export(map.highway.nodes)
+S3method(summary,highway.node.map)
+S3method(print,highway.node.map)
+export(as.highway.net)
+S3method(summary,highway.net)
+S3method(print,highway.net)
+S3method(print,sum.highway.net)
+
+# Assignment Set operation functions
+export(free.flow)
+export(build.paths)
+export(load.paths)
+export(build.and.load.paths)
+export(intercept.paths)
+export(skim.paths)
+
+# Assignment Set constrution functions
+export(cost.integrator)
+export(build.serious.objective.function)
+export(build.easy.objective.function)
+export(new.assignment.set)
+export(make.assignment.class)
+export(add.assignment.class)
+export(build.BPR.function)
+export(new.intercept.set)
+
+# High-level algorithms
+export(highway.assign)
+
+# Algorithm Configuration Utilities
+export(parse.control)
+export(build.equil.stats.function)
+export(build.convergence.test)
+export(build.intercept.function)
+
+# Assignment Algorithm Helpers
+export(weighted.update.diff)
+export(weighted.update)
+export(weighted.update.intercept)
+export(build.lambda.function)
+export(build.lambda.search)
+
+# Utility functions
+export(rmse)
+export(ipf)
+S3method(print,iterative.fit)
+
+# Low-level path functions
+export(.build.network.set)
+export(.shortest.paths)
+export(.load.paths)
+export(.skim.paths)
+export(.intercept.paths)
+export(.walk.paths)

Added: pre-alpha/travelr/R/Assignment_Set.R
===================================================================
--- pre-alpha/travelr/R/Assignment_Set.R	                        (rev 0)
+++ pre-alpha/travelr/R/Assignment_Set.R	2010-04-26 02:15:14 UTC (rev 9)
@@ -0,0 +1,267 @@
+####################################################################################################
+#   travelr\r\Assignment_Set.R by Jeremy Raw  Copyright (C) 2010
+# 
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+# 
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+# 
+#   A copy of the GNU General Public License is available at
+#   http://www.r-project.org/Licenses/
+#   and included in the R distribution (in directory ‘share/licenses’).
+####################################################################################################
+
+# This file defines the Assignment Set mechanism for handling single and multi-class assignment
+# and presenting an abstract interface for assignment algorithms.
+
+# The assignment.set structures define the network and demand that will be fitted to each other
+# during the assignment process.  The basic operations used in the assignment algorithms are
+# the following (implemented as generic functions):
+#   1. cost.function (uses assignment.set cost.function; generates cost.set from volume.set)
+#   2. build.paths (uses assignment.set network.set; generates path.set from cost.set)
+#   3. load.demand (uses assignment.set demands; generates volume.set from path.set)
+
+
+# free.flow will produce a volume set of zero flow for use in computing free-flow costs
+
+free.flow<-function(aset)
+	as.data.frame(
+	lapply(aset$classes,
+		   FUN=function(aclass,numLinks) vector("numeric",numLinks),
+		   numLinks=aset$network$numLinks)
+	)
+
+# build.paths will produce a list of path sets from route costs
+
+build.paths<-function(aset,costs)
+	mapply(function(ac,cost) .shortest.paths(ac$network,cost),
+	       aset$classes,costs,
+		   USE.NAMES=TRUE, # Keep assignment class names on resulting list of path structures
+		   SIMPLIFY=FALSE) # and leave results as a list
+
+# load.paths will map demand matrices onto path sets
+
+load.paths<-function(aset,paths)
+	as.data.frame(mapply(function(ac,paths) .load.paths(paths,ac$demand),
+			      aset$classes, paths,
+			      USE.NAMES=TRUE, # Keep assignment class names on resulting volume set list
+			      SIMPLIFY=FALSE) # and leave the results as a list
+			     )
+
+# build.and.load.paths provides a slight efficiency boost by leaving more of the
+# logic at C-level.  It's useful in link-based assignment, but not in path-based assignment
+#    where one is equilibrating path sets, rather than volume sets
+build.and.load.paths<-function(aset,costs) {
+	result=mapply( function(ac,cost).build.and.load.paths(ac$network,cost,ac$demand),
+				   aset$classes,costs,
+				   USE.NAMES=TRUE, # Keep assignment class names on resulting volume set list
+				   SIMPLIFY=FALSE) # and leave results as a list (each element of which is
+				                   # a list with "paths" and "volumes" elements)
+	paths = lapply(result,function(p)p$paths)      # Paths for all classes in one list
+	volumes = lapply(result,function(p)p$volumes)  # Volumes for all classes in one list
+	return( list(paths=paths, volumes=volumes, result=result) )
+}
+
+# intercept.paths returns a list of OD matrices ("od") whose paths intercept selected links
+# we get one intercept structure per assignment class
+intercept.paths<-function( paths, links )
+	lapply( paths,FUN=.intercept.paths,links=links )
+
+# skim paths returns a demand matrix by performing a function on a set of numeric values
+# corresponding to the links on each path between origin and destination
+skim.paths<-function( paths, costs, empty.val=0.0, FUN="sum" ) {
+	if ( FUN=="sum" )
+		return( .skim.paths(paths,costs,empty.val) )
+	else
+		stop("Not implemented: Skims using functions other than 'sum'")
+}
+
+# Assignment Set
+# This factory function builds a suitable object of one of the assignment set types
+# The assignment sets themselves will need usable functions for computing diagnostic functions
+
+# Structure of an assignment set:
+
+# list with these elements:
+#     network
+#     cost.function (optional; default is to call a cost function for each class)
+#     objective.function (optional; default is to do vector inner product of cost set and volume set)
+#     ff.vol (free flow volume - zero flow in all classes)
+#     ff.cost (cost function evaluated at zero flow in all classes)
+#     classes (named list of assignment classes, each with these elements):
+#           network.set -- routable subset of the network for assignment
+#           demand (matrix)
+#           cost.function (optional if assignment set cost function is defined)
+
+cost.integrator <- function( cost.function, ff.vol, ff.cost, cong.vol, cong.cost, depth=0, tol=1e-4, max.depth=7, ... ) {
+	# Integrate the cost function using adaptive quadrature with inside-interval tolerance testing
+	# The tolerance indicates how close the mid-segment interpolated cost must be to its true value in order to avoid
+	# recursing into each of the sub-intervals
+	# May do as many as 2^depth+1 calls to cost.function 
+	mid.cost  <- (cong.cost-ff.cost)/2
+	vol.ivl   <- (cong.vol-ff.vol)/2
+	mid.vol   <- ff.vol+vol.ivl
+	true.cost <- cost.function(mid.vol)
+	test.diff <- abs(mid.cost-true.cost)/true.cost
+	if ( test.diff < tol || depth>=max.depth )  # limit on recursion
+		result <- sum(vol.ivl*((ff.cost+true.cost)+(true.cost+cong.cost)))/2 # area for this part
+	else {
+		depth <- depth+1
+		result<- Recall( cost.function, ff.cost, ff.vol, mid.vol, true.cost, tol=tol, depth=depth, ... ) +
+	             Recall( cost.function, true.cost, mid.vol, cong.vol, cong.cost, tol=tol, depth=depth, ... )
+	}
+	attr(result,"max.depth")<-depth
+	return(result)
+}
+
+# "correct" objective function
+build.serious.objective.function<-function(cost.function, ff.vol, ff.cost, tol=1e-4, ...)
+	function(cost, volume) cost.integrator(cost.function, ff.vol, ff.cost, volume, cost, tol, ...)
+
+# expedient objective function
+build.easy.objective.function<-function(cost.function, ff.vol, ff.cost, ...)
+	function(cost,volume) sum( cost*volume )
+
+# Construct an assignment.set with all the right pieces in all the right places
+new.assignment.set <- function( network,classes,cost.volume.type=c("vector","matrix"),cost.function=NULL,
+	make.objective.function=NULL, obj.tol=1e-4, ... ) {
+	# We need cost functions to handle either a list/data.frame of volumes or a numeric vector
+	# But we would like to figure out how to determine whether to boil down the volumes to a vector
+	#  or pass the entire data.frame.
+	# Suggestion: if any class provides a cost function, all of them receive a data.frame
+	#   in that case, for classes with no cost function, the assignment set cost function should
+	#   have a default process that takes a volume vector and returns a cost vector the unequipped class
+	# If only the assignment set provides a cost function, then the function receives a vector
+
+	# Check network
+	if ( is.null(network) || class(network) != "highway.net" )
+		stop("Assignment set requires a highway network")
+	aset<-list(network=network)
+
+	# Check cost.volume.type and construct a function to transform the volumes appropriately
+	if ( length(cost.volume.type)>1 ) # Use default: "vector"
+			cost.volume.type<-cost.volume.type[1]
+	if ( ! (cost.volume.type%in%c("vector","matrix") ) )
+		stop("Assignment set: Need vector or matrix cost function type.")
+	if (cost.volume.type=="vector")
+		cost.volume<-function(volume) rowSums(volume) # volume should be a matrix or data.frame
+	else
+		cost.volume<-function(volume) volume
+
+	# Install cost function
+	if ( is.null(cost.function) ) {
+		# if there is no function for the assignment set, there must be one for each assignment class
+		# cost functions for the assignment class get only a single parameter (volume)
+		# cost functions for the assignment set are passed two parameters (volume,aset)
+		if ( any(sapply( classes, function(x) { if (is.null(x$cost.function)) TRUE; FALSE } ) ) )
+			stop("Assignment set is missing required cost.functions")
+		# the master cost function will then apply the individual class functions
+		aset$cost.function<-function(volume,aset) {
+			as.data.frame(lapply(aset$classes,FUN=function(x,VOL) x$cost.function(cost.volume(VOL)),VOL=volume))
+			}
+	} else {
+		# There is great flexibility in how the cost function works
+		# It can call individual cost functions for only certain classes, build a partial matrix of
+		# volumes with only certain classes, etc. (Set the cost.volume.type to "matrix" in that case)
+		aset$cost.function<-function(volume,aset) {
+			as.data.frame(cost.function(cost.volume(volume),aset))
+			}
+	}
+
+	# Check classes
+	if ( !is.list(classes) )
+		stop("Assignment set cannot be built from non-list 'classes'")
+
+	# Validate presence of required class elements
+	bad.names <-names(classes)!=sapply(classes,function(x){ ifelse(is.null(x$name),"", x$name)})
+	if ( any(bad.names) )
+		stop("Assignment set classes do not have consistent names:\n",paste(names(classes)[bad.names],collapse=", "))
+	missing.cost.function<-sapply( classes, function(x) { if (is.null(x$cost.function)) TRUE; FALSE } )
+	if ( !all(missing.cost.function) && any(missing.cost.function) )
+		message("Assignment set classes have incomplete class functions:",paste(names(classes)[missing.cost.function],collapse=", "))
+	missing.demand<-sapply( classes, function(x) { if (is.null(x$demand)) TRUE; FALSE } )
+	if ( any(missing.demand) )
+		stop("Assignment set classes are missing required demand matrix:",paste(names(classes)[missing.demand],collapse=", "))
+	missing.network.set<-sapply( classes, function(x) { if (is.null(x$network.set)||class(x$network.set)!="highway.network.set") TRUE; FALSE } )
+	if ( any(missing.network.set) )
+		stop("Assignment set classes are missing suitable network set:",paste(names(classes)[missing.demand],collapse=", "))
+	aset$classes<-classes
+
+	# Construct free-flow volume and cost, and build objective function for assignment
+	aset$ff.vol<-free.flow(aset)
+	aset$ff.cost<-aset$cost.function(aset$ff.vol)
+	if ( is.null(make.objective.function) ) make.objective.function<-build.easy.objective.function
+	aset$objective.function<-make.objective.function(cost.function,aset$ff.vol, aset$ff.cost, obj.tol,...)
+
+	# Mark this as a known structure
+	class(aset)<-"highway.assignment.set"
+
+	return(aset)
+}
+
+# The following convenience functions make it easy to build a set of assignment classes, once
+# you know the parameters for each class.
+# Example:
+#     class.list <- vector("list")
+#     aclass <- make.assignment.class(network,"Class.1",demand.1,links.1,penalty.subset.1,cost.function.1)
+#     class.list<-add.assignment.class(class.list,aclass) # preserves name, unlike c(...)
+#     aclass <- make.assignment.class(network,"Class.2",demand.2,links.2,penalty.subset.2,cost.function.2)
+#     class.list<-add.assignment.class(class.list,aclass)
+#     ## etc...
+
+make.assignment.class <- function( network, name, demand, link.subset=TRUE, penalty.subset=NULL, cost.function=NULL ) {
+	aclass<-list( name=name,demand=demand )
+	aclass$network.set<-.build.network.set(network,link.subset,penalty.subset)
+	if (!is.null(cost.function)) aclass$cost.function<-cost.function
+	return(aclass)
+}
+
+# Create a named list entry with the same name as the class
+# Do not permit a new class to overwrite an existing class with the same name
+# There are easy ways to remove the existing name first (e.g. class.list[[replace.name]]<-NULL)
+add.assignment.class <- function( classes, aclass ) {
+	if ( all(match(aclass$name,names(classes),nomatch=0)==0) ) {
+		classes[[aclass$name]]<-aclass
+	} else {
+		warning("Cannot overwrite existing class",aclass$name)
+	}
+	return(classes)
+}
+
+# Cost functions
+# Here is a helper to make a BPR-type function, using some static data
+# This particular version wants a vector of volumes, which is suitable
+# if we need a vector-based cost function
+build.BPR.function <- function(cost.data) {
+	# cost.data is either an environment, a list, or a data.frame
+	# All-cap elements must be provided in cost.data
+	# (either as scalars or as vectors that supply a value for each link in the network)
+	return( with(cost.data, function(volume){TIME * ( 1 + (ALPHA/(CAPACITY^BETA)) * ( volume^BETA ) )} ) )
+}
+
+# Intercept Set, describes select link processing
+
+# The intercept set is built at the level of the highway assignment
+	# intercept is an intercept set that will assemble the intercept link volumes
+	#    typically, this will be applied to all groups and we'll end up with a demand
+	#    array with Z x Z x numSets dimensions, and a links matrix with L x numSets
+	#    rows and columns attached to the intercept set
+
+new.intercept.set <- function( links, filter.od=NULL ) {
+	# links is a logical or numeric vector indexing the network links -- any path between
+	# selected OD pairs that crosses one of these links is marked for inclusion by the
+	# intercept.paths function.
+	# filter.od takes a logical OD matrix and keeps only those OD pairs that are interesting
+	#    (default is to keep all origins and destinations)
+	if ( mode(links)!="logical" ) stop("links parameter for intercept.set must be a logical vector")
+	if (is.null(filter.od)) filter.od<-function(od) return(od)
+	iset <- list(links=links,filter.od=filter.od)
+	attr(iset,"class")<-"intercept.set"
+	return(iset)
+}
+

Added: pre-alpha/travelr/R/Highway_Assignment.R
===================================================================
--- pre-alpha/travelr/R/Highway_Assignment.R	                        (rev 0)
+++ pre-alpha/travelr/R/Highway_Assignment.R	2010-04-26 02:15:14 UTC (rev 9)
@@ -0,0 +1,280 @@
+####################################################################################################
+#   travelr\r\Highway_Assignment.R by Jeremy Raw  Copyright (C) 2010
+# 
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+# 
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+# 
+#   A copy of the GNU General Public License is available at
+#   http://www.r-project.org/Licenses/
+#   and included in the R distribution (in directory ‘share/licenses’).
+####################################################################################################
+
+# This file defines the basic highway assignment mechanism and a series of algorithms, using the
+# single/multi-class assignment sets to provide a set of abstract capabilities for evaluating
+# the algorithms.
+
+highway.assign <- function( aset, method=c("AON","MSA","Frank.Wolfe","ParTan"), control=vector("list",0) ) {
+	# simple error-checking driver for highway assignment methods
+	# aset: data for the assignment (network, demand, cost function)
+	# method: Certain standard methods are defined in this module, and if the value of method (a character
+	#   string) does not find a function with a name of "highway.assign.<<method>>", it will
+	#   attempt to locate a built-in to perform the actual assignment (the built-ins are named
+	#   ".highway.assign.<<method>>" and are hidden in the package namespace.
+    # control: a list of control parameters:
+	#   intercept=NULL     # an intercept.set object; intercept results will be returned, if supported by 'method'
+    #   min.relative.gap=1e-4  # value below which algorithm has "converged"
+    #   max.iter=100       # maximum number of assignment iterations
+    #   max.time=600       # maximum algorithm runtime in seconds (for the impatient)
+    #   opt.tol=.Machine$double.eps^0.5 # tolerance for internal line search
+    #   verbose=0:5        # how much detail to report interactively during the run (bigger number ==> more wordy)
+	#   log=FALSE          # if true, accumulates and returns a data.frame of iteration statistics
+    # the implementation method should return a HighwayAssignment object (q.v.)
+
+	# First check the aset parameter
+	if (class(aset)!="highway.assignment.set")
+		stop("Must provide an assignment.set object as first parameter (aset)")
+	# Then find the appropriate method
+	if (length(method)>1) {
+		method<-method[1] # which means the default method is the first in the built-in list
+		cat(sprintf(gettext("Using default assignment method '%s'\n"),as.character(method)))
+	}
+	method<-paste("highway.assignment.",as.character(method),sep="") # user can override by defining a suitable function
+	a.func<-NULL
+	if ( exists(method,mode="function") ) {
+		a.func <- get(method,mode="function")
+	} else {
+		method<-paste(".",method,sep="")
+		if ( exists(method,mode="function") ) a.func<-get(method,mode="function")
+	}
+	if ( is.null(a.func) )
+		stop(paste(gettext("Could not find assignment method implementation"),sprintf("'%s'",method)))
+	return(c(a.func(aset,control),list(method=method)))
+}
+
+# Parse control variable, including global defaults
+# You'll get the value explicitly set, or the local default in the algorithm, or the global default
+# from the control.defaults list
+# Note that some algorithms like MSA may provide alternate "global" defaults
+1
+control.defaults <- list(
+		intercept=NULL,
+		min.rgap        = 1e-4,               # stop if relative.gap goes below this value
+		max.iter        = 100,				 # for MSA, it's overridden to 4 and other stopping conditions are ignored
+										     # set max.iter=0 for open-ended running
+		max.elapsed     = 3600,				 # number of seconds - set to zero to run forever
+		opt.tol         = .Machine$double.eps^0.5, # tolerance for internal line searches
+		verbose         = 1,				 # depending on the algorithm, bigger numbers will produce more tracking output
+		log             = FALSE				 # if true, return 'log' attribute (a data.frame) with assignment result details
+	)
+
+parse.control<-function( control, control.element, default.value=NULL ) {
+	element<-control[[control.element]]
+	if ( !is.null(element) ) {
+		result<-element
+		default<-FALSE
+	} else {
+		default<-TRUE
+		if ( !is.null(default.value) ) result<-default.value
+		else                           result<-control.defaults[[control.element]]
+	}
+	if ( !is.null(result) ) attr(result,"default")<-default # can't put attribute on NULL
+	return(result)
+}
+
+# Build a function to compute equilibrium statistics from user-supplied objective function
+# 'flow' is a scalar representing the sum of all demands
+# Computations based on Boyce, Ralevic-Dekic, and Bar-Gera, presentation at
+# at the 16th Annual International EMME/2 Users’ Group Conference, Albuquerque, NM,
+# March 18-20, 2002.
+build.equil.stats.function <- function( objective.function, flow=0 ) {
+	avg.excess.cost.func <-  ifelse( all.equal(flow,0), function(gap)NULL, function(gap)gap/flow )
+	start.time=proc.time()["elapsed"]
+	run.time <- function() as.numeric(proc.time["elapsed"]-start.time)
+
+	# add additional values to the result list when calling this function
+	# by providing named numeric scalar values in the ... parameter
+	function( iter, cost, vol, diff, best.lower.bound=as.numeric(NA), ... ) {
+		# vol  is the vector of Equilibrium Path Volumes
+		# diff is the vector of Shortest Path Volumes - Equilibrium Path Volumes
+		# cost is the vector of link costs given current equilibrium path volumes
+		iter             <-  iter
+		elapsed          <-  run.time()
+		objective.value  <-  objective.function(cost,vol)
+		gap              <-  -sum(cost*diff)
+		lower.bound      <-  objective.value-gap
+		best.lower.bound <-  max( best.lower.bound, lower.bound, na.rm=TRUE )
+		relative.gap     <-  -gap/abs(best.lower.bound)
+		avg.excess.cost  <-  avg.excess.cost.func(gap)
+
+		# The return form here produces a named vector of numeric values
+		return ( c(iter=iter,elapsed=elapsed,
+				   objective=objective.value,gap=gap,relative.gap=relative.gap,
+				   lower.bound=lower.bound,best.lower.bound=best.lower.bound,
+				   avg.excess.cost=avg.excess.cost,...) )
+	}
+}
+
+# To go with the equilibrium stats function, we need a function to test for convergence
+# This function is extensible, and will use any control parameters that have prefixes
+# of "min." or "max." with the rest of their name matching the equilibrium statistic names
+build.convergence.test<-function( control, test=c("min.relative.gap","max.iter","max.elapsed") ) {
+	# pre-compute a setup for convergence testing
+	test.min.names <- test[which(grep("^min\\.",test))]
+	test.max.names <- test[which(grep("^max\\.",test))]
+	min.names<-gsub("^min\\.","",test.min.names)
+	max.names<-gsub("^max\\.","",test.max.names)
+	test.min<-unlist(control[test.min.names])
+	test.max<-unlist(control[test.max.names])
+	function(results) {
+		all( results[min.names] < test.min ) &&
+		all( results[max.names] > test.max )
+	}
+}
+
+# Built-In Assignment Methods
+
+# Not much error checking on built-in functions, since they are hidden in the namespace
+#  and should only be called through the highway.assignment driver function, which
+#  should do more checking
+.highway.assignment.AON <- function(aset,control) {
+	verbose <- parse.control(control,"verbose")
+	if ( verbose>0 )
+		cat("All-or-Nothing (AON) highway assignment\n")
+	assign.results<-build.and.load.paths(aset,aset$ff.cost)
+	cat("Assignment results in AON:\n")
+	intercept<-(build.intercept.function(iset<-parse.control(control,"intercept"),aset))(assign.results$paths)
+	return(list(aset=aset,costs=aset$ff.cost,
+				paths=assign.results$paths,volumes=assign.results$volumes,
+				iset=iset,intercept=intercept))
+}
+
+# Built-In Assignment Methods
+.highway.assignment.MSA <- function(aset,control) {
+
+	log <- parse.control(control,"log")
+	if ( log ) {
+		log.function<-function(new.results,log,...) {
+			if ( is.data.frame(log) )
+				return( rbind(log,as.data.frame(new.results)) )
+			else
+				return( new.results )
+		}
+	} else
+		log.function<-function(new.results,log,...) FALSE
+
+	verbose <- parse.control(control,"verbose")
+	if ( verbose>0 )
+		cat("Multiple Successive Averages (MSA) highway assignment\n")
+	max.iter <- parse.control(control,"max.iter",4)
+	if (attr(max.iter,"default")) {
+		cat(sprintf(gettext("Maximum iterations for MSA assignment set to default (%d)\n"),max.iter))
+	} else if ( verbose>0 ) {
+		cat(sprintf("Maximum iterations for MSA assignment set to %d\n"),max.iter)
+	}
+	control$max.iter<-max.iter
+
+	# Construct helper values and functions
+	flow<-sum(sapply(aset$aclasses,function(ac)sum(ac$demand),SIMPLIFY=TRUE,USE.NAMES=FALSE))
+	build.intercepts<-build.intercept.function(iset<-parse.control(control,"intercept"),aset)
+	converged<-build.convergence.test(control,c("max.iter"))
+	equil.stats<-build.equil.stats.function(aset$objective.function, flow)
+
+	iter<-1
+	vol  <- aset$ff.vol
+	cost <- aset$ff.cost
+	intercept<-NULL
+	best.lower.bound<-as.numeric(NA)
+	while (TRUE) {
+		load<-build.and.load.paths(aset,cost)
+		vol.new<-load$volumes
+		vol.diff <- vol.new-vol
+		new.results <- equil.stats(iter,cost,vol,vol.diff,best.lower.bound)
+		best.lower.bound<-new.results$best.lower.bound
+
+		iter<-iter+1
+		iter.weight <- 1/(iter+1)
+		vol <- weighted.update.diff( iter.weight, vol, vol.diff )
+		intercept<- weighted.update.intercept( iter.weight, intercept, build.intercepts(load$paths) )
+		cost<-aset$cost.function(vol)
+
+		# Still not a happy logging process...
+		log <- log.function( new.results, log )
+		if ( nrow(log)>0 && verbose>1 )
+			print(log,nrow(log))         # TODO: a prettier job on this output
+		if ( converged(new.results) )
+			break
+	}
+	return(list(aset=aset,costs=cost,paths=load$paths,volumes=load$volumes,iset=iset,intercept=intercept,results=new.results,log=log))
+}
+
+# Pre-build the intercept function (to avoid if-test inside inner loop)
+build.intercept.function <- function( iset, aset ) {
+	if ( is.null(iset) ) f <- function(paths) NULL
+	else {
+		classes <- aset$classes   # list of matrices, one for each assignment class
+		demand <- lapply(classes,function(x) x$demand)
+		cat("Demand while building intercept function:\n")
+		links <- iset$links     # vector of links of interest
+		f <- function(paths) {
+			od <- intercept.paths(paths,links)		# each OD here is just 0/1
+			od <- mapply( function(od,d)d*od,       # Expand to list of OD matrices
+						  od,demand,
+						  USE.NAMES=TRUE, SIMPLIFY=FALSE )
+			volumes <- mapply(.load.paths,
+						  paths,demand,
+						  USE.NAMES=TRUE, SIMPLIFY=FALSE )
+			return( list(od=od,volumes=volumes) )	# OD is list of matrices, per assignment class
+													# volumes is list of volumes, per assignment class
+		}
+	}
+	return( f )
+}
+
+# Helper functions for updating equilibrium iterations
+
+# Weighted update functions combine iteration values
+
+weighted.update.diff <- function( weight, base, new.diff ) base + weight * new.diff
+
+weighted.update <- function( weight, base, new ) {
+	if ( is.null(base) ) result <- new # ignoring weight in this case
+	else result <- base*(1-weight) + weight * new
+	return( result )
+}
+
+inner.weighted.intercept.function <- function(b,n,weight) {
+	list( od      = weighted.update(weight,b$od,n$od),
+		  volumes = weighted.update(weight,b$volumes,n$volumes) )
+}
+
+weighted.update.intercept<-function( weight, base, new ) {
+	if ( is.null(base) || is.null(new) ) return(new)
+	return( mapply(base,new,inner.weighted.intercept.function,vol=,SIMPLIFY=FALSE,weight=weight) )
+}
+
+# line search helper functions
+# Do like this before starting any assignment iterations:
+#      lambda.func<-make.lambda.func(cost.function,objective.function)
+#      lambda.search<-make.lambda.search(lambda.func,opt.tol=1e-8)
+# Then inside the iterations, when you need to find a lambda value, do this:
+#      search.result<-lambda.search(volume,difference)
+#      updated.volume <- volume + search.result$lambda * diff
+#      updated.objective <- search.result$objective
+# Look at the Frank-Wolfe or ParTan algorithms to see applications
+
+build.lambda.function<-function(cost.function,objective.function)
+	function( lambda, vol, diff ) objective.function(cost.function(vol+lambda*diff),diff)
+
+build.lambda.search<-function(lambda.func,opt.tol=.Machine$double.eps^0.25) {
+	function(vol,diff) {
+		search.result<-uniroot(lambda.func,interval=c(0,1),tol=opt.tol,vol=vol,diff=diff)
+		return(list(lambda=search.result$root,objective=search.result$f.root))
+	}
+}

Added: pre-alpha/travelr/R/Highway_Network.R
===================================================================
--- pre-alpha/travelr/R/Highway_Network.R	                        (rev 0)
+++ pre-alpha/travelr/R/Highway_Network.R	2010-04-26 02:15:14 UTC (rev 9)
@@ -0,0 +1,145 @@
+####################################################################################################
+#   travelr\r\Highway_Network.R by Jeremy Raw  Copyright (C) 2010
+# 
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+# 
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+# 
+#   A copy of the GNU General Public License is available at
+#   http://www.r-project.org/Licenses/
+#   and included in the R distribution (in directory ‘share/licenses’).
+####################################################################################################
+
+# Highway network functions
+
+# map.highway.nodes constructs a node map object for the network
+#    map<-map.highway.net(links)
+#    links$A.mapped<-map$map(links$A)
+#    all(links$A==map$unmap(links$A.mapped))
+
+# links should be a dataset or matrix with one row per network link
+# nodes is a list of node identifiers (integer) used to
+# identify ends of the links
+
+# Link.fields identify the "From" and "To" node identifiers.  These should
+# be scalar values suitable for use as a list index.
+
+# Why NOT just use factors for the node identifiers, and make the "mapped" node fields into
+# the integer factor code?
+
+map.highway.nodes <- function( nodes, numZones ) {
+
+	# Extract nodes in use from the network
+	if ( any(nodes[1:numZones]!=c(1:numZones)) ) stop("Must have all zones (1 to",numZones,") present in node list")
+		# Path building will be unhappy and issue a warning if there are destination zones
+		#   that don't exist and thus can't be reached; plus you'll waste a lot memory and processing time
+		#   building path trees that have nothing in them.
+	node.map<-sort(unique(c(1:numZones,nodes[nodes>numZones])))
+	base.index<-0
+
+	# Construct functions with suitable closure for node mapping and unmapping
+	map.env<-new.env(parent=parent.frame(2))	  # skip this function's environment
+	assign("node.map",node.map,envir=map.env)     # but preserve necessary values
+	assign("base.index",base.index,envir=map.env)
+	map<-function(n) match(n,node.map,nomatch=base.index)
+	unmap<-function(n) node.map[n]
+	environment(map)<-map.env
+	environment(unmap)<-map.env
+
+	nmap<-list(map=map,unmap=unmap)
+	attr(nmap,"numNodes")<-length(node.map)
+	attr(nmap,"maxRawNode")<-max(node.map)
+	attr(nmap,"class")<-"highway.node.map"
+	return(nmap)
+}
+summary.highway.node.map<-function(object,...) return(unlist(attributes(object)[c("numNodes","maxRawNode")]))
+print.highway.node.map<-function(x,...) print(summary(x))
+length.highway.node.map<-function(x) return(attr(x,"maxRawNode"))
+
[TRUNCATED]

To get the complete diff run:
    svnlook diff /svnroot/travelr -r 9


More information about the Travelr-commits mailing list