Table of Contents
A tour through GroIMP interfaces
In the following, we will use the same model in different GroIMP user interfaces.
Additional User interfacesBesides the Graphical user interface that is used in GroIMP by default, it is possible to execute models in other ways. This can be useful for specific scenarios such as larger pipelines or remote execution. In general a user interface describes how the user communicates with the software (GroIMP).
For example with the graphical user interface windows and panels are used for this communication:
If you are not sure what interface you need for your application you can consider the following page: What interface do I need |
The model
To highlight the flow of simulations across different interfaces, we use a simple model of a plant that can grow apically and, with a second function, split one bud into two, each with half the strength. The idea is to observe how different indicators change over 10 growth stages, depending on when the buds split.
- model.rgg
module A(float len) extends Sphere(0.1) { {setShader(GREEN);} } int step=0; module Leaf(float l,float w) extends Parallelogram(l,w){ {setShader(GREEN);} public float getArea(){ return l*w; } } protected void init () [ { step=0; dataset("data").clear(); chart(dataset("data"),XY_PLOT); } Axiom ==> A(1); ] public void grow () [ A(x) ==> F(x) [[M(-x/2)[for(int i=0; i<6; i++)(RH(60)[RL(70)Leaf(1,0.2)])]]]RH(137)A(x*0.9); Leaf(l,w) ==>Leaf(l*1.1,w); { dataset("data").addRow() .set(0,step) .set(1,count((*F*))) .set(2,sum((*F*)[length])) .set(3,mean((*F*)[length])) .set(4,count((*Leaf*))) .set(5,sum((*Leaf*).getArea())) .set(6,mean((*Leaf*).getArea())); step++; } ] public void split()[ A(x) ==> [RL(30) A(x/2)][RL(-30) A(x/2)]; ]
GUI
To use this model in the default interface (GUI) we can just press the grow button 10 times and trigger the split in between.
In order to make this a bit more elegant and easier to use we can also create a new function:
public void test(){ reset(); //go back to init and clean the dataset for(apply(5)){ grow(); } for(apply(1)){ split(); } for(apply(5)){ grow(); } }
Or even with a slider:
public @Editable @Range(min=0, max=9) int splitPoint=5; public void test(){ reset(); for(apply(splitPoint)){ grow(); } for(apply(1)){ split(); } for(apply(10-splitPoint)){ grow(); } }
Headless
Now that we have a function that only requires us to press one button to get all steps, we are already closer to run it completely automatically. The simplistic way to do so is using the headless mode.
Headless modeGroIMP can be started without its graphical interface, in a non interactive headless mode. This mode can either execute a specified model and or a specified GroIMP command and can therefore be used for simple automation or as a base for e.g. the HTTP server.
If a model is started in headless, GroIMP executes it,logs the console output and afterwards shut down. Capturing any feedback from the simulation must be included in the model. |
In this mode, GroIMP starts executing a model or a command and closes again after it is done. This way we can execute our model with only one system call. For that to work, we have to take care of three things, first GroIMP knows what to do with the model once it's opened, then the model closes itself at the end and finally, we can get the data out of it.
startup
To make GroIMP do something at headless starting we have to overwrite the startup function of our model. The startup function is executed every time the model is started (also in the GUI!).
protected void startup(){ super.startup(); //this starts the default startup function which is needed to make the model work //make sure what happens next only happens in headless if (de.grogra.pf.boot.Main.getProperty("headless") != null){ runLater(null); } }
To make sure that all components are properly loaded before we start our simulation we do not directly push our model commands but we call runLater()
which tells GroIMP that as soon as everything is ready it should execute the function run(Object info)
.
run(Object info)
In this run function, we must describe everything that is supposed to happen in the simulation, including closing the software.
public void run(Object info){ test();// run our chain of functions System.exit(0); //close GroIMP }
We could now run this with java -Xverify:none -jar core.jar –headless PATH/TO/THE/MODEL.gsz
and we would see: Nothing
Even though GroIMP ran the simulation to completion we did not define any output for our data. There are several ways to do this using Java, but for this tutorial, we are going with one of the simplest. We are exporting the dataset we already created to a CSV file and saving it in the same directory as the project.
public void run(Object info){ test();// run our chain of functions java.io.FileWriter fw = new java.io.FileWriter(getWD()+"data.csv"); //create a java file writer for a file called data.csv in the working directory(where the project is stored) dataset("data").export(fw,","); // export our dataset data with comma as seperator fw.close(); // close the file writer System.exit(0); //close GroIMP }
Parameters
GroIMP gives us the ability to forward custom parameters from the command line, in general, these are model-independent but in headless this is no issue since we have to start GroIMP for every model anyway.
The usage of this is quite simple, every value we add with -X<key>=value will be added to Main.getProperty(<key>). So in our case: if we put -Xsp=6 to the command line we can use:
public void run(Object info){ splitPoint = Integer.valueOf (Main.getProperty("sp")); test(); java.io.FileWriter fw = new java.io.FileWriter(getWD()+"data.csv"); dataset("data").export(fw); fw.close(); System.exit(0); }
And to make the csv a bit clearer:
protected void init ()[ Axiom ==> A(1); {time=0; dataset("data").clear(); dataset("data").addRow() .setText(0,"time") .setText(2,"sumFlength") .setText(3,"meanFlength") .setText(4,"countLeaf") .setText(5,"sumLeafgetArea") .setText(6,"meanLeaf getArea"); } ]
This setup would now allow us to integrate the model into pipelines, e.g. an R script:
splitPoint <-5 setwd("\path\to\projectFolder") System(paste("C:\Path\to\your\java -jar core.jar -xp=",splitPoint," --headless Project.gsz")) data <- read.csv("data.csv", header = TRUE, sep = ",")
HTTP
With a bit of scripting, we could use the headless mode also for orchestration or automation, yet we would start and stop GroIMP every time which of course takes a bit of time. To avoid this, and to also allow us to execute on a remote server or in parallel on a GroIMP instance we can use the HTTP-Server
HTTP serverIt is possible to start GroIMP as a HTTP server to then send model paths via HTTP for GroIMP to execute. After GroIMP executed the model values can be returned to the HTTP client who send the request. It is possible to send several models in series or in parallel.
This allows the non interactive execution(similar to headless mode) through e.g. a web browser. |
Minimal required preparations
If we want to run our model through the HTTP-server we have to make three small changes first:
1. Change the condition in the startup function, to make sure it runs if the model was opened through the http server.
To do so we test if we have an HTTPResponse object, this object is created when the model is opened through the server and holds the request from the client and the ability to respond.
protected void startup(){ super.startup(); //check if we could get a HTTPResponse Object if( de.grogra.imp.net.HttpResponse.get(workbench())!=null){ runLater(null); } }
2. Make sure that we are not looking for the headless parameter anymore (its not there).
we have to make sure we removed the line 'splitPoint = Integer.valueOf (Main.getProperty(“sp”));' since it would otherwise crash.
3. We have to stop stopping GroIMP
In the headless mode, we finished the run(Object info) function with 'System.exit(0)' to close GroIMP, in the HTTP server we do not want to do that. Therefore we have to remove this line.
First execution
To open a model through the HTTPServer we have to provide the path to the gsz file, this path is relative to a given “root directory”. We can set this base directory in the GroIMP preferences under “HTTP > open Project”, an easy solution is to just set this to the directory of your project.
After we define our root directory we can start the HTTP server through the main menu through “Net > Open HTTP Server”. In most cases we do not care about the port number, if you want/need to change it you can do so without any issues just keep chaining it in the examples below as well.
If the server is running we can visit http://localhost:58080 to see the installed plugins and possible HTTP commands.
The command we are interested in is open (and yes is the only one ). To open we have to provide the path to the model in the given style:
http://localhost:58080/open?path/to/your/model.gsz
So if you use the project directory as a root directory of the http server, the call would be:
http://localhost:58080/open?http_model.gsz
If we run this, its open and runs the model and then stops. The model stays open and the request in the browser keeps loading because the browser never gets any feedback.
To give the browser feedback we need to use the send function from the HttpResponse object at the end of our run function:
de.grogra.imp.net.HttpResponse.get(workbench()).send(false);
The false value in the function defines that we do not intend to send information back. Therefore this will only give us “The content has not been set.” as a message in the browser.
LazyHTTP
To send content back and to also receive parameters we could now work with the HttpResponse object, but to make this a bit easier we will use the LazyHTTP plugin which can be installed through the plugin manager:
import de.grogra.imp.net.lazyhttp.*; ... protected void startup(){ super.startup(); CsvWrapper resp = CsvWrapper.get(workbench()); if(resp!=null){ runLater(resp); } } public void run(Object info){ CsvWrapper resp = (CsvWrapper)info; test(); resp.append(dataset("data")); resp.send(); closeWorkbench(); //close the project at the end } ...
Additionally, we closed the workbench at the end of our execution because we do not need it anymore now since we got the data in the browser.
Parameters
Using the LazyHttp library we can also parse given values from the HTTP request:
public void run(Object info){ CsvWrapper resp = (CsvWrapper)info; splitPoint = resp.get("sp",5); //get the value of sp or the default value of 5 ...
To give a parameter like this to the HTTP server we add '&sp=3' to our URL:
Integration in R
groIMP.HTTP <- function(path, param, url="http://localhost:58080"){ parameters="" for (name in names(param)) { parameters <-paste(parameters,"&",name,"=",param[name],sep="") } req <- httr::GET(paste(url,"/open?",path,parameters,sep="")) return(httr::content(req,"text")) } groIMP.CSV <- function(path, param, url="http://localhost:58080"){ data <- groIMP.HTTP(path,param,url) return(read.table(text = data, sep =",", header = TRUE, stringsAsFactors = FALSE)) } df <- groIMP.CSV("l_http_model.gsz",list(sp=3))
API
If you require more interaction with the model during the simulation or want to have direct access to what happens we can use the GroIMP API.
APIGroIMP can be started as an HTTP API that provides a generalized set of commands allowing interaction from other software. Currently a Python and a R library are provided but any software capable of HTTP requests could interact with GroIMP. This approach can be used to automatically start a simulation and interact with it during the execution.
|
To start the API is a bit more complicated than the HTTP-server, an instruction can be found here: Start GroIMP in API mode.
The API works without any changes in the GroIMP model, by using similar functionalities than the GUI, we can run RGG functions, execute XL queries, trigger import or export or view datasets.
The full list of commands can be found here: https://gitlab.com/grogra/groimp-plugins/api/-/blob/master/commands.md
And a browser based tutorial here
Additionally GroIMP provides two libraries to connect to the API:
Application on our model
library(dplyr) library(httr2) library(GroR) wb<-GroLink.open("http://localhost:58081/api",content = readBin("/home/tim/Dokumente/ssc25/talks/GroLink/exampels/tut/cut.gsz", "raw", 10e6)) WBRef.runRGGFunction(wb,"test") data<-WBRef.getDataset(wb,"data") df <- read.table(text = data, sep =",", header = TRUE, stringsAsFactors = FALSE) WBRef.runXLQuery(wb,"Model.INSTANCE.splitPoint:=1;") WBRef.runRGGFunction(wb,"test") data<-WBRef.getDataset(wb,"data") df2 <- read.table(text = data, sep =",", header = TRUE, stringsAsFactors = FALSE) plot(df$sumLeafgetArea) plot(df2$sumLeafgetArea)