Writing a Java Client

NEOS runs an XML-RPC server that can communicate with clients written in a variety of languages including Java. As of July 2016, the NEOS XML-RPC server will accept HTTPS submissions at the URL https://neos-server.org:3333. HTTP submissions to http://neos-server.org:3332 will be phased out. There are two packages available to facilitate the process of writing a Java application to submit jobs to NEOS via XML-RPC. The org.neos.client package contains methods to submit jobs to NEOS and to retrieve results. The org.neos.gams package contains methods to facilitate working with GAMS models in Java.

Contents

Download the NEOS Java packages: neos.jar

This is a new neos.jar file as of February 16, 2017 that should fix the error message “Failed to create input stream: Unexpected end of file from server.” Please contact us if you continue to experience problems. Thank you for your patience.

Using the org.neos.client package

In this section, we provide samples of Java code to illustrate how to use the classes and their methods in the org.neos.client package to solve a GAMS model of a chemical equilibrium problem. The first two code samples use the standard interface to submit jobs to NEOS. The standard interface is a simple interface that comes in two versions, a blocking version and a non-blocking version. The third code sample uses the advanced interface to submit jobs to NEOS. The advanced interface allows developers to call any of the other NEOS XML-RPC API methods and is recommended only for advanced users. In all three code samples, the problem is read from the file ChemEq.txt and is solved using the nonlinear programming solver KNITRO.

Standard Interface

The standard interface for submitting jobs to NEOS comes in two versions, a blocking version and a non-blocking version.

Blocking Version

In this example, the NeosClient class is used to connect to the NEOS XML-RPC server. The NeosJobXml class is used to create the job XML in the format required by the solver KNITRO. The job is submitted to the server using the submitJob method of the NeosClient class. The results are retrieved using the NeosJob class and are printed to standard output.


package org.neos.example;

import org.neos.client.FileUtils;
import org.neos.client.NeosClient;
import org.neos.client.NeosJobXml;
import org.neos.client.NeosJob;

public class ExampleMain {	
  /* set the HOST and the PORT fields of the NEOS XML-RPC server */
   private static final String HOST="neos-server.org";
   private static final String PORT="3333";
	
   public static void main(String[] args){
      /* create NeosClient object client with server information */
      NeosClient client = new NeosClient(HOST, PORT);
		
      /* create NeosJobXml object exJob with problem type nco for nonlinearly */
      /* constrained optimization, KNITRO for the solver, GAMS for the input */
      NeosJobXml exJob = new NeosJobXml("nco", "KNITRO", "GAMS");
		
      /* create FileUtils object to facilitate reading model file ChemEq.txt */
      /* into a string called example */
      FileUtils fileUtils = FileUtils.getInstance(FileUtils.APPLICATION_MODE);
      String example = fileUtils.readFile("ChemEq.txt");
      /* add contents of string example to model field of job XML */
      exJob.addParam("model", example);

      /* call submitJob() method with string representation of job XML */
      NeosJob job = client.submitJob(exJob.toXMLString());
      /* print results to standard output */
      System.out.println(job.getResult());
   }
}

The classes in the org.neos.client package reference the following external libraries: Commons-logging-1.1.jar, Jcommon-1.0.16.jar, Ws-commons-util-1.0.2.jar, Xmlrpc-client-3.1.3.jar, and Xmlrpc-common-3.1.3.jar.

Back to top

Non-Blocking Version

In this example, the job is submitted to the server using the non-blocking version of the submitJob method called submitJobNonBlocking. There are three modification to the code above. First, create a class that implements the interface ResultCallback.


import org.neos.client.ResultCallback;

public class JobPrinter implements ResultCallback {
	public void handleJobInfo(int jobNo, String pass) {
		System.out.println("Job Number : " + jobNo);
		System.out.println("Password   : " + pass);
	}
	public void handleFinalResult(String results) {
		System.out.println(results);
	}
	
}

Second, after line “exJob.addParam(“model”, example);”, add the following:

JobPrinter jobPrinter = new JobPrinter();

Finally, modify the line “NeosJob job = client.submitJob(exJob.toXMLString());” to the following:

NeosJob job = client.submitJobNonBlocking(exJob.toXMLString(), jobPrinter);

Back to top

Advanced Interface

The advanced interface uses the NeosXmlRpcClient class to submit jobs to NEOS and it allows developers to call any of the other NEOS XML-RPC API methods. This interface should be used only by advanced users. In the code sample below, the chemical equilibrium problem is read from the file ChemEq.txt and is submitted to NEOS by invoking the submitJob method on the server and waiting for a response. The ResultReceiver class is the recommended method for retrieving the job output. The callback method will be invoked automatically when the the final result is ready.


package org.neos.example;

import org.neos.client.ResultCallback;
/* create a class that implements ResultCallback interface */
public class JobReceiver implements ResultCallback {
   public void handleJobInfo(int jobNo, String pass) {
      System.out.println("Job Number : " + jobNo);
      System.out.println("Password   : " + pass);
   }	
   public void handleFinalResult(String results) {
      System.out.println(results);
   }
}

package org.neos.example;

import java.util.Vector;

import org.apache.xmlrpc.XmlRpcException;
import org.neos.client.FileUtils;
import org.neos.client.NeosJobXml;
import org.neos.client.NeosXmlRpcClient;
import org.neos.client.ResultReceiver;

public class ExampleMain {
   private static final String HOST="neos-server.org";
   private static final String PORT="3333";

   public static void main(String[] args){
      /* create NeosXmlRpcClient object with server information */
      NeosXmlRpcClient client = new NeosXmlRpcClient(HOST, PORT);

      /* create NeosJobXml object exJob with problem type nco for nonlinearly */
      /* constrained optimization, KNITRO for the solver, GAMS for the input */		
      NeosJobXml exJob = new NeosJobXml("nco", "KNITRO", "GAMS");
		
      /* create FileUtils object to facilitate reading model file ChemEq.txt */
      /* into a string called example */
      FileUtils fileUtils = FileUtils.getInstance(FileUtils.APPLICATION_MODE);
      String example = fileUtils.readFile("ChemEq.txt");
      /* add contents of string example to model field of job XML */
      exJob.addParam("model", example);

      /* convert job XML to string and add it to the parameter vector */		
      Vector params = new Vector();
      String jobString = exJob.toXMLString();
      params.add(jobString);
		
      Integer currentJob = 0;
      String currentPassword = "";
      String result = "";
		
      try {
         client.connect();
         /* invoke submitJob method on server and wait for response for 5000 ms */
         Object[] results = (Object[]) client.execute("submitJob", params, 5000);

         /* get returned values of job number and job password */
         currentJob = (Integer) results[0];
         currentPassword = (String) results[1];		
         System.out.println("submitted" + results);
					
         /* initialize receiver and start output monitoring */
         JobReceiver jobReceiver = new JobReceiver();
         ResultReceiver receiver = new ResultReceiver(client, jobReceiver, 
                               currentJob, currentPassword);
         receiver.run();
			
         System.out.println(receiver.getResult());			
      } catch (XmlRpcException e) {
         System.out.println("Error submitting job :" + e.getMessage());
         return;
      }
      /* print results to standard output */
      System.out.println(result);		
   }		
}

Back to top

GAMS GDX Support

GDX, GAMS Data eXchange, is a binary file format to put data into and to retrieve data from GAMS. The NEOS Server supports GDX as an input format; the FileUtils class in the org.neos.client package provides a method for reading a binary file. Below is an example of a GAMS model that uses GDX and a code sample to read the model file and to create the job XML. Note that the model file must include commands to open the GDX file ($GDXIN in.gdx) and load the symbols ($LOAD n f).

dietdgx.mod

$GDXIN in.gdx
Sets n nutrients
     f foods;
$LOAD n f

Parameter b(n) required daily allowances of nutrients
          a(f,n) nutritive value of foods (per dollar spent);
$LOAD b a

Positive Variable x(f)  dollars of food f to be purchased daily   (dollars)
Free     Variable cost  total food bill                           (dollars)
Equations  nb(n) nutrient balance  (units),  cb cost balance  (dollars) ;
nb(n).. sum(f, a(f,n)*x(f)) =g= b(n);  
cb..  cost =e= sum(f, x(f));
Model diet stiglers diet problem / nb,cb /;
Solve diet minimizing cost using lp;

Code Sample: read GDX file and create job XML

FileUtils fileUtils = FileUtils.getInstance(FileUtils.APPLICATION_MODE);
String model = fileUtils.readFile("resources/dietgdx.mod");

/* Read gdx file into byte array */
byte[] gdx = fileUtils.readBinaryFile("resources/diet.gdx");
 
NeosJobXml dietJob = new NeosJobXml("nco", "MINOS", "GAMS");
dietJob.addParam("model", model);

/* Add GDX into parameter */
dietJob.addBinaryParam("gdx", gdx);  

Back to top

Using the org.neos.gams package

In the code samples above, the GAMS model was read from a file into a string that was loaded into the model portion of the job XML. In the case of a dynamic application, however, it may be necessary to build the model within the Java program. The org.neos.gams package contains input data methods to facilitate the model building process; the package also includes an example SolutionParser, code to parse the output returned by GAMS in order to reformat or reuse the job results.

Input Data Methods

Within a dynamic application, the set members and the data values may change from run to run. The org.neos.gams package supports three types of data structures used in GAMS models, sets, scalars, and parameters, and provides methods to generate strings that are properly formatted for each type.

Sets

In GAMS, sets correspond to the indices in an algebraic representation of a model. Suppose we have a set of country codes with three elements: UK, US, and TH. In GAMS, the format of the set statement is:
Set cc country code /UK, US, TH/;
The org.neos.gams package includes a class Set that takes a name and a text description:
Set countryCode = new Set("cc", "country code");
Then, members of the set can be added individually by the addValue method:

countryCode.addValue("UK");
countryCode.addValue("US");
countryCode.addValue("TH");

The result is a properly formatted string:
Set cc country code /UK, US, TH/;

Scalars

A scalar is a data value that is not defined with respect to a set. The org.neos.gams package includes a class Scalar that takes a name, a text description, and a value. The code

Scalar city = new Scalar("city", "City", "Madison");

results in the properly formatted string

Scalar city City /Madison/;

Parameters

Parameters are data values that are defined with respect to sets. The data may be one-dimensional or multi-dimensional. The org.neos.gams package includes a class Parameter that takes a name with the set dependency and a text description. Then, the (key, value) pairs can be added using the add method.

Parameter capital = new Parameter("cap(cc)", "Capital of");
capital.add("TH", "Bangkok");
capital.add("US", "Washington D.C");
capital.add("UK", "London");

The result is

Parameter cap(cc) Capital of /
TH Bangkok
US Washington D.C
UK London
/;

The Parameter structure can be used to create multi-dimensional parameter data; to do so, separate the key for each dimension by a dot when adding components.

Parameter cost = new Parameter("cost(i,j)", "Cost");
cost.add("ipad.wifi", "499");
cost.add("ipad.3g", "629");

The result is

Parameter cost(i,j) cost /
ipad .wifi = 499
ipad .3g = 629 /;

Back to top

Output Data Parser

The org.neos.gams package includes an example class SolutionParser, which takes as input a string that is the output of a GAMS job, parses the string, and returns a structure through which the job results can be accessed. Below is an example of output from a GAMS job, a sample of code showing how the parsed information can be used, and the corresponding output of the code sample.

Output from a GAMS job

**** SOLVER STATUS     1 Normal Completion         
**** MODEL STATUS      1 Optimal                   
**** OBJECTIVE VALUE                2.2452

---- VAR xx  shipment quantities in cases

                      LOWER     LEVEL     UPPER    MARGINAL

seattle  .new-york      .       50.000     +INF       .         
seattle  .chicago       .      300.000     +INF       .         
seattle  .topeka        .         .        +INF      0.036      
san-diego.new-york      .      275.000     +INF       .         
san-diego.chicago       .         .        +INF      0.009      
san-diego.topeka        .      275.000     +INF       .

                      LOWER     LEVEL     UPPER    MARGINAL

---- EQU cost           -INF      2.245     +INF       .          

 cost  total food bill                           (dollars)

Code Sample

The following code sample shows the call to the SolutionParser and its methods to display information.

SolutionParser parser = new SolutionParser(results);

System.out.printf("Solver Status [%d] %s n", parser.getSolverStatusCode(), 
   parser.getSolverStatus());
System.out.printf("Model Status [%d] %s n", parser.getModelStatusCode(), 
   parser.getModelStatus());
System.out.printf("Objective :%f nn", parser.getObjective());
 
SolutionData cost = parser.getSymbol("cost", SolutionData.EQU, 0);
SolutionRow row = cost.getRows().get(0);
System.out.printf("Cost :%f nn" , row.getLevel());
 
SolutionData xx = parser.getSymbol("xx", SolutionData.VAR, 2);
for(SolutionRow row : xx.getRows()) {
 System.out.printf("%s %s - %fn" , row.getIndex(0),row.getIndex(1), 
   row.getLevel());
}

Output from Code Sample

Solver Status [1]  Normal Completion          
Model Status [1]  Optimal                    
Objective :2.245200

Cost :2.245
 
seattle new-york - 50.000000
seattle chicago - 300.000000
seattle topeka - 0.000000
san-diego new-york - 275.000000
san-diego chicago - 0.000000
san-diego topeka - 275.000000