2017-03-02 1 views
0

Ich verschachtelte mehrere Ebenen von do.call (jede selbst verwendet Funktionen in den Parametern, nicht hart-codiert) in einer parallelisierten Umgebung %dopar% und eine Funktion von meiner äußeren Umgebung kann nicht durch die innerste Funktion gefunden werden. Ich kenne den .export Parameter auf foreach und benutze es, aber irgendwie verbreitet sich die benannte Funktion nicht über die gesamte Kette.Verschachtelte do.call innerhalb einer foreach% dopar% Umgebung kann keine mit export übergebene Funktion finden

Ich reduzierte meine Frage an den folgenden Testfall, der dieses Problem nicht aufweist:

library(doParallel) 
cl <- makeCluster(4) 
registerDoParallel(cl) 

simple.func <- function(a, b) { 
    return(a+b) 
} 

inner.func <- function(a, b) { 
    return(do.call(simple.func, list(a=a, b=b))) 
} 

outer.func <- function(a, b, my.func=inner.func) { 
    return(do.call(my.func, list(a=a, b=b))) 
} 

main.func <- function(my.list=1:10, my.func=outer.func, 
         my.args=list(my.func=inner.func)) { 
    results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE, 
        .export="simple.func") %dopar% { 
    return(do.call(my.func, c(list(a=i, b=i+1), my.args))) 
    } 
    return(results) 
} 

Anstatt die richtige Antwort zu geben (eine Liste mit einigen Zahlen), erhalte ich:

Error in { : task 1 failed - "object 'simple.func' not found" 

Hinzufügen von if (!exists("simple.func")) stop("Could not find parse.data in scope main.func") an den Anfang jeder Funktion (Ändern des Namens des Bereichs als geeignet) ergibt, dass es inner.func ist, die simple.func nicht sehen - obwohl outer.functut sehen Sie es.

Ich habe auch ein paar Varianten des Obigen getestet, entweder mit main.func oder outer.func mit der nächsten Ebene Funktion hart-codiert, anstatt es von einem Parameter zu verwenden. Beide dieser Variationen funktionieren (z. B. geben das erwartete Ergebnis an), aber für den realen Fall möchte ich die Generalisierbarkeit behalten, die Unterfunktionen als Parameter zu verwenden.

# Variation number one: Replace main.func() with this version 
main.func <- function(my.list=1:10, my.func=outer.func, 
         my.args=list(my.func=inner.func)) { 
    results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE, 
        .export=c("simple.func", "outer.func", "inner.func")) %dopar% { 
    return(do.call(outer.func, list(a=i, b=i+1, my.func=inner.func))) 
    } 
    return(results) 
} 

# Variation number two: Replace outer.func() and main.func() with these versions 
outer.func <- function(a, b, my.func=inner.func) { 
    return(do.call(inner.func, list(a=a, b=b))) 
} 

main.func <- function(my.list=1:10, my.func=outer.func, 
         my.args=list(my.func=inner.func)) { 
    results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE, 
        .export=c("simple.func", "inner.func")) %dopar% { 
    return(do.call(my.func, c(list(a=i, b=i+1), my.args))) 
    } 
    return(results) 
} 

ich auch simple.func unten in der Kette passieren könnte manuell, indem sie sie als zusätzliche Parameter einschließlich, aber das sieht besonders chaotisch, und warum sollte es notwendig sein, wenn simple.func sollte zusammen als Teil der Umwelt nur weitergegeben werden?

# Variation number three: Replace inner.func(), outer.func(), and main.func() 
# with these versions 
inner.func <- function(a, b, innermost.func=simple.func) { 
    return(do.call(innermost.func, list(a=a, b=b))) 
} 

outer.func <- function(a, b, my.func=inner.func, 
         innermost.args=list(innermost.func=simple.func)) { 
    return(do.call(my.func, c(list(a=a, b=b), innermost.args))) 
} 

main.func <- function(my.list=1:10, my.func=outer.func, 
         my.args=list(my.func=inner.func, 
         innermost.args=list(innermost.func=simple.func))) { 
    results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE, 
        .export="simple.func") %dopar% { 
    return(do.call(my.func, c(list(a=i, b=i+1), my.args))) 
    } 
    return(results) 
} 

Hat jemand Ideen für weniger kludgy Lösungen oder die zugrunde liegende Ursache für dieses Problem?

+0

Nur eine Anmerkung, da Ihr Code voll von ihnen ist: alle Rückkehr-Anrufe sind nicht notwendig. R gibt automatisch den Wert des letzten Ausdrucks in einer Funktion zurück. Sie brauchen nur 'return', um eine Funktion vorzeitig zu verlassen. –

Antwort

0

Für doParallel, und andere doNnn Adapter, der nicht den aktuellen Prozess nicht gabeln, denke ich, die folgende hacken es tun würde:

main.func <- function(my.list = 1:10, my.func=outer.func, 
         my.args = list(my.func=inner.func)) { 
    results <- foreach(i = my.list, .multicombine = TRUE, .inorder = FALSE, 
        .export="simple.func") %dopar% { 
    environment(my.args$my.func) <- environment() ## <= HACK 
    return(do.call(my.func, args = c(list(a=i, b=i+1), my.args))) 
    } 
    return(results) 
} 

Alternativ können Sie den doFuture Adapter (I‘ m der Autor). Dann müssen Sie sich keine Gedanken um globale Objekte machen, da sie automatisch erkannt und exportiert werden. Das heißt, es ist nicht erforderlich, .export (oder .packages) anzugeben. Zum Beispiel, in Ihrem Fall die folgenden Werke:

library("doFuture") 
registerDoFuture() 
plan(multisession, workers = 4) 

main.func <- function(my.list = 1:10, my.func = outer.func, 
         my.args = list(my.func = inner.func)) { 
    foreach(i = my.list, .multicombine = TRUE, .inorder = FALSE) %dopar% { 
    do.call(my.func, args = c(list(a = i, b = i+1), my.args)) 
    } 
} 

res <- main.func(1:3) 
str(res) 
## List of 10 
## $ : num 3 
## $ : num 5 
## $ : num 7 

Sie können auch überspringen foreach() die ganze Zeit und zu tun:

library("future") 
plan(multisession, workers = 4) 

main <- function(my.list = 1:10, my.func = outer.func, 
       my.args = list(my.func = inner.func)) { 
    future_lapply(my.list, FUN = function(i) { 
    do.call(my.func, args = c(list(a = i, b = i+1), my.args)) 
    }) 
} 

PS. Es gibt viele verschiedene plan() Backends zur Auswahl. Die einzige, die nicht abgedeckt ist, ist, wenn Sie doRedis verwenden.

+0

Der Hack "environment()" scheint zu funktionieren, aber die Verwendung der doFuture-Bibliothek gibt manchmal den folgenden Fehler (beim Ausführen des Plans (cluster, works = cl) zum Laden eines Clusters): "Fehler in UseMethod (" Tweak "): keine anwendbare Methode für "Tweak" angewendet auf ein Objekt der Klasse "function" ". Gibt es eine einfache Antwort dafür, oder sollte ich eine neue Frage öffnen (oder einen Bug an deinen GitHub senden)? – Randall

+0

Hmm ... könnte es sein, dass Sie ein anderes Paket angehängt haben, das eine andere 'Cluster'-Funktion definiert? Was sagt 'str (cluster)' zu dem es gehört? Ist 'plan (" cluster ", worker = cl)' oder 'plan (future :: cluster, worker = cl)' fix? Wenn nicht, erstellen Sie bitte ein Problem auf https://github.com/HenrikBengtsson/future, damit wir herausfinden können, was vor sich geht. Danke, dass du mich informiert hast. – HenrikB

Verwandte Themen