BCL in .NET
Topics covered in this chapter:
New object-oriented solver API
In Xpress 9.4 the .NET API to the Xpress Solver was extended to allow the creation of an optimization problem in a more object-oriented fashion. The new API is fully integrated with the low-level Xpress Solver API, including full support for nonlinear problems, and has been designed for high performance. The new API is a replacement for BCL, which will be deprecated in future Xpress releases.
For more information, see the Solver .NET User Guide.
An overview of BCL in .NET
The .NET interface of BCL provides the full functionality of the C version, targeting .NET Framework 3.5 and .Net Standard 2.0, or higher, for Windows and Linux platforms. The C modeling objects, such as variables, constraints and problems, are again converted into classes, and their associated functions into methods of the corresponding class in .NET.
In .NET, the termwise definition of constraints is simplified by the overloading of the algebraic operators like '+`, '-`, '<=`, or '==` as in the C++ interface. With these operators constraints may be written in a form that is close to an algebraic formulation. Also, for printing output, it is possible to use both .NET native IO functions or the XPRBprob.printF BCL function (which corresponds to the BCL C XPRBprintf function).
Care must be taken when using operators like '+=` with BCL objects. According to the .NET language definition, an expression like 'a += b` is always resolved as 'a = a + b`. This usually requires creating a deep copy of 'a`, updating the copy with 'b` and finally assigning the copy to 'a`. If 'a` is a big object, such as a long expression or constraint, then creating this deep copy may create significant overhead. In this cases it is more efficient to use 'add()` or 'addTerm()` functions of the 'XPRBexpr` or 'XPRBctr` class. Both functions manipulate the expression/constraint directly and thus avoid the deep copy.
The names of classes and methods have been adapted to .NET naming standards: all .NET classes that have a direct correspondence with modeling objects in BCL (namely XPRBprob, XPRBvar, XPRBctr, XPRBcut, XPRBsol, XPRBsos, XPRBbasis) take the same names, with the exception of XPRBidxset which becomes XPRBindexSetin .NET. The names of the methods are also changed by dropping the prefix XPRB and references to the type of the object, and each word is capitalized. For example, function XPRBgetvarname is turned into the method getName of class XPRBvar. Two exceptions are XPRBreadlinecb and XPRBreadarrlinecb which maintain these names as methods of the XPRBprob class and thus become XPRBprob.XPRBreadline and XPRBprob.XPRBreadarrline. The auto-completion feature in Visual Studio .NET can be used to obtain a full list of class methods and properties and prototypes of each method.
The BCL functionality is exposed through the XPRB and XPRBprob classes, which reside in the BCL namespace, which resides in the xprbdn assembly.
The C++ classes and their methods documented in section C++ class reference correspond to a large extent with the classes defined by the .NET interface, with some additional classes in the .NET version. A comprehensive documentation of the BCL .NET interface is available online at the BCL .NET Library Reference section.
Referencing BCL from a .NET project
The .NET interface for BCL is distributed as a NuGet package in the lib/nuget folder of the Xpress installation directory. The easiest way to make this available to your .NET projects is to add this folder as a local package source. If using Visual Studio, you can add this folder as a NuGet package source by choosing NuGet Package Manager -> Package Manager Settings from the Tools menu, then select Package Sources. Click the + icon to add a new source, name it "Xpress local packages" and select the folder. Click Update then OK to save the changes.
Then, to add the BCL library to a Visual Studio project, select Manage NuGet Packages from the Project menu. Select "Xpress local packages" from the Package source drop-down in the top right, then select "FICO.Xpress.XPRBdn" from the Browse tab, and click Install to add it to the project.
Or if using the .NET CLI, you can add the Mosel .NET wrapper library to your project as follows:
dotnet add package -s <XPRESS_INSTALL_DIRECTORY>/lib/nuget FICO.Xpress.XPRBdn
In all cases, the .NET Interface for BCL does not include the Xpress native libraries and so a local installation of Xpress will be required to use BCL from .NET.
Example
An example of the use of BCL in .NET is the following, which again constructs the example described in Chapter Modeling with BCL. Contrary to the C and C++ versions, BCL .NET needs to be initialized explicitly by calling the static method XPRB.init().
BCL models can take up large amounts of memory therefore, if a BCL .NET model is embedded into an application, we recommend to explicitly release the resources used by the XPRBprob object, once it is no longer needed, by calling XPRB.Dispose()(particularly, the memory used by the underlying C structures that are not taken into account by the automated garbage collection in .NET). Alternatively, a problem can be reset to free up the memory used by the optimization and solution data without removing the problem definition itself.
using BCL; namespace Examples { public class xbexpl1 { const int NJ = 4; /* Number of jobs */ const int NT = 10; /* Time limit */ static double[] DUR = {3,4,2,2}; /* Durations of jobs */ static XPRBvar[] start = new XPRBvar[NJ]; /* Start times of jobs */ static XPRBvar[,] delta = new XPRBvar[NJ,NT]; /* Binaries for start times */ static XPRBvar z; /* Maximum completion time (makespan) */ static XPRBprob p; static void jobsModel() { XPRBexpr le; int j,t; /****VARIABLES****/ /* Create start time variables */ for(j=0;j<NJ;j++) start[j] = p.newVar("start"); z = p.newVar("z",BCLconstant.XPRB_PL,0,NT); /* Declare the makespan variable */ for(j=0;j<NJ;j++) /* Declare binaries for each job */ for(t=0;t<(NT-DUR[j]+1);t++) delta[j,t] = p.newVar("delta" + (j+1) + (t+1),BCLconstant.XPRB_BV); /****CONSTRAINTS****/ for(j=0;j<NJ;j++) /* Calculate maximal completion time */ p.newCtr("Makespan", start[j]+DUR[j] <= z); p.newCtr("Prec", start[0]+DUR[0] <= start[2]); /* Precedence relation between jobs */ for(j=0;j<NJ;j++) /* Linking start times and binaries */ { le = new XPRBexpr(0); for(t=0;t<(NT-DUR[j]+1);t++) le += (t+1)*delta[j,t]; p.newCtr("Link_" + (j+1), le == start[j]); } for(j=0;j<NJ;j++) /* One unique start time for each job */ { le = new XPRBexpr(0); for(t=0;t<(NT-DUR[j]+1);t++) le += delta[j,t]; p.newCtr("One_" + (j+1), le == 1); } /****OBJECTIVE****/ /* Define and set objective function */ p.setObj(p.newCtr("OBJ", new XPRBrelation(z))); /****BOUNDS****/ for(j=0;j<NJ;j++) start[j].setUB(NT-DUR[j]+1); /* Upper bounds on start time variables */ } static void jobsSolve() { int j,t,statmip; for(j=0;j<NJ;j++) for(t=0;t<NT-DUR[j]+1;t++) delta[j,t].setDir(BCLconstant.XPRB_PR,10*(t+1)); /* Give highest priority to variables for earlier start times */ p.setSense(BCLconstant.XPRB_MINIM); p.mipOptimize(); /* Solve the problem as MIP */ statmip = p.getMIPStat(); /* Get the MIP problem status */ if((statmip == BCLconstant.XPRB_MIP_SOLUTION) || (statmip == BCLconstant.XPRB_MIP_OPTIMAL)) { /* An integer solution has been found */ Console.WriteLine("Objective: {0}", p.getObjVal()); for(j=0;j<NJ;j++) { /* Print the solution for all start times */ System.Console.WriteLine("{0}: {1}", start[j].getName(), start[j].getSol()); for(t=0;t<NT-DUR[j]+1;t++) System.Console.Write("{0}: {1} ",delta[j,t].getName(),delta[j,t].getSol()); System.Console.WriteLine(); } } } public static void Main() { XPRB.init(); /* Initialize BCL */ p = new XPRBprob("Jobs"); /* Create a new problem */ jobsModel(); /* Basic problem definition */ jobsSolve(); /* Solve and print solution */ return; } } }
The definition of SOS is similar to the definition of constraints.
static XPRBsos[] set = new XPRBsos[NJ]; /* Sets regrouping start times for jobs */ static XPRBprob p; public static void jobsModel() { ... for(j=0;j<NJ;j++) /* Declare binaries for each job */ for(t=0;t<(NT-DUR[j]+1);t++) delta[j,t] = p.newVar("delta" + (j+1) + (t+1),BCLconstant.XPRB_PL,0,1); /****SETS****/ for(j=0;j<NJ;j++) { le = new XPRBexpr(0); for(t=0;t<(NT-DUR[j]+1);t++) le += (t+1)*delta[j,t]; set[j] = p.newSos("sosj",BCLconstant.XPRB_S1,le); } }
Branching directives for the SOSs are added as follows.
for(j=0;j<NJ;j++) set[j].setDir(BCLconstant.XPRB_DN); /* First branch downwards on sets */
Adding the following two lines during or after the problem definition will print the problem to the standard output and export the matrix to a file respectively.
p.print(); /* Print out the problem definition */ p.exportProb(BCLconstant.XPRB_MPS,"expl1"); /* Output matrix to MPS file */
Similarly to what has been shown for the problem formulation in C and C++, we may read data from file and use index sets in the problem formulation. Only a few changes and additions to the basic model formulation are required for the creation and use of index sets. However, if we want to read in a data file in the format accepted by the C functions XPRBreadlinecb and XPRBreadarrlinecb (that is, using '!' as commentary sign, and ',' as separators, and skip blanks and empty lines), we need to configure the data file access in .NET.
In the following program listing we leave out the method jobsSolve because it remains unchanged from the previous.
using System.IO; using BCL; namespace Examples { public class xbexpl1i { const int MAXNJ = 4; /* Max. number of jobs */ const int NT = 10; /* Time limit */ //Define XPRBDATAPATH to whatever folder you wish. const string XPRBDATAPATH = "../../data"; const string DATAFILE = XPRBDATAPATH + "/jobs/durations.dat"; /**** DATA ****/ static int NJ = 0; /* Number of jobs read in */ static double[] DUR = new double[MAXNJ]; /* Durations of jobs */ static XPRBindexSet Jobs; /* Job names */ static XPRBvar[] start; /* Start times of jobs */ static XPRBvar[,] delta; /* Binaries for start times */ static XPRBvar z; /* Maximum completion time (makespan) */ XPRBprob p; /* BCL problem */ static void readData() { string name; FileStream file; StreamReader fileStreamIn; /* Create a new index set */ Jobs = p.newIndexSet("jobs", MAXNJ); file = new FileStream(DATAFILE, FileMode.Open, FileAccess.Read); fileStreamIn = new StreamReader(file); object[] tempobj = new object[2]; while((NJ<MAXNJ) && (p.XPRBreadarrline(fileStreamIn, 99, "{t} , {g} ", out tempobj, 1) == 2)) { int dummy; name = (string)tempobj[0]; DUR[NJ] = (double)tempobj[1]; dummy = Jobs + name; NJ++; } fileStreamIn.Close(); file.Close(); System.Console.WriteLine("Number of jobs read: " + Jobs.getSize()); } static void jobsModel() { XPRBexpr le; int j,t; /****VARIABLES****/ /* Create start time variables (incl. bounds) */ start = new XPRBvar[NJ]; if(start==null) { System.Console.WriteLine("Not enough memory for 'start' variables."); return; } for (j = 0; j < NJ; j++) start[j] = p.newVar("start", BCLconstant.XPRB_PL, 0, NT - DUR[j] + 1); z = p.newVar("z",BCLconstant.XPRB_PL,0,NT); /* Declare the makespan variable */ delta = new XPRBvar[NJ, NT]; for(j=0;j<NJ;j++) /* Declare binaries for each job */ for(t=0;t<(NT-DUR[j]+1);t++) delta[j,t] = p.newVar("delta"+Jobs.getIndexName(j)+"_"+(t+1), BCLconstant.XPRB_BV); /****CONSTRAINTS****/ for(j=0;j<NJ;j++) /* Calculate maximal completion time */ p.newCtr("Makespan", start[j]+DUR[j] <= z); p.newCtr("Prec", start[0]+DUR[0] <= start[2]); /* Precedence relation between jobs */ for(j=0;j<NJ;j++) /* Linking start times and binaries */ { le = new XPRBexpr(0); for(t=0;t<(NT-DUR[j]+1);t++) le += (t+1)*delta[j,t]; p.newCtr("Link_" + (j+1), le == start[j]); } for(j=0;j<NJ;j++) /* One unique start time for each job */ { le = new XPRBexpr(0); for(t=0;t<(NT-DUR[j]+1);t++) le += delta[j,t]; p.newCtr("One_" + (j+1), le == 1); } /****OBJECTIVE****/ p.setObj(p.newCtr(z)); /* Define and set objective function */ jobsSolve(); /* Solve the problem */ } public static void Main() { XPRB.init(); p = new XPRBprob("Jobs"); /* Create a new problem */ readData(); /* Read in the data */ jobsModel(); /* Basic problem definition */ } } }
QCQP Example
The following is an implementation with BCL .Net of the QCQP example described in Section Example:
using BCL; namespace Examples { public class xbexpl1i { const int MAXNJ = 4; /* Max. number of jobs */ const int NT = 10; /* Time limit */ //Define XPRBDATAPATH to whatever folder you wish. const string XPRBDATAPATH = "../../data"; const string DATAFILE = XPRBDATAPATH + "/jobs/durations.dat"; /**** DATA ****/ static int NJ = 0; /* Number of jobs read in */ static double[] DUR = new double[MAXNJ]; /* Durations of jobs */ static XPRBindexSet Jobs; /* Job names */ static XPRBvar[] start; /* Start times of jobs */ static XPRBvar[,] delta; /* Binaries for start times */ static XPRBvar z; /* Maximum completion time (makespan) */ XPRBprob p; /* BCL problem */ static void readData() { string name; FileStream file; StreamReader fileStreamIn; /* Create a new index set */ Jobs = p.newIndexSet("jobs", MAXNJ); file = new FileStream(DATAFILE, FileMode.Open, FileAccess.Read); fileStreamIn = new StreamReader(file); object[] tempobj = new object[2]; while((NJ<MAXNJ) && (p.XPRBreadarrline(fileStreamIn, 99, "{t} , {g} ", out tempobj, 1) == 2)) { int dummy; name = (string)tempobj[0]; DUR[NJ] = (double)tempobj[1]; dummy = Jobs + name; NJ++; } fileStreamIn.Close(); file.Close(); System.Console.WriteLine("Number of jobs read: " + Jobs.getSize()); } static void jobsModel() { XPRBexpr le; int j,t; /****VARIABLES****/ /* Create start time variables (incl. bounds) */ start = new XPRBvar[NJ]; if(start==null) { System.Console.WriteLine("Not enough memory for 'start' variables."); return; } for (j = 0; j < NJ; j++) start[j] = p.newVar("start", BCLconstant.XPRB_PL, 0, NT - DUR[j] + 1); z = p.newVar("z",BCLconstant.XPRB_PL,0,NT); /* Declare the makespan variable */ delta = new XPRBvar[NJ, NT]; for(j=0;j<NJ;j++) /* Declare binaries for each job */ for(t=0;t<(NT-DUR[j]+1);t++) delta[j,t] = p.newVar("delta" + Jobs.getIndexName(j) + "_" + (t+1), BCLconstant.XPRB_BV); /****CONSTRAINTS****/ for(j=0;j<NJ;j++) /* Calculate maximal completion time */ p.newCtr("Makespan", start[j]+DUR[j] <= z); p.newCtr("Prec", start[0]+DUR[0] <= start[2]); /* Precedence relation between jobs */ for(j=0;j<NJ;j++) /* Linking start times and binaries */ { le = new XPRBexpr(0); for(t=0;t<(NT-DUR[j]+1);t++) le += (t+1)*delta[j,t]; p.newCtr("Link_" + (j+1), le == start[j]); } for(j=0;j<NJ;j++) /* One unique start time for each job */ { le = new XPRBexpr(0); for(t=0;t<(NT-DUR[j]+1);t++) le += delta[j,t]; p.newCtr("One_" + (j+1), le == 1); } /****OBJECTIVE****/ p.setObj(p.newCtr(z)); /* Define and set objective function */ jobsSolve(); /* Solve the problem */ } void jobsSolve() { int j,t,statmip; for(j=0;j<NJ;j++) for(t=0;t<NT-DUR[j]+1;t++) delta[j,t].setDir(BCLconstant.XPRB_PR,10*(t+1)); /* Give highest priority to variables for earlier start times */ p.setSense(BCLconstant.XPRB_MINIM); p.mipOptimize(); /* Solve the problem as MIP */ statmip = p.getMIPStat(); /* Get the MIP problem status */ if((statmip == BCLconstant.XPRB_MIP_SOLUTION) || (statmip == BCLconstant.XPRB_MIP_OPTIMAL)) { /* An integer solution has been found */ System.Console.WriteLine("Objective: " + p.getObjVal()); for(j=0;j<NJ;j++) { /* Print the solution for all start times */ System.Console.WriteLine(start[j].getName() + ": " + start[j].getSol()); for(t=0;t<NT-DUR[j]+1;t++) System.Console.Write(delta[j,t].getName()+ ": "+delta[j,t].getSol()+" "); System.Console.WriteLine(); } } } public static void Main() { XPRB.init(); p = new XPRBprob("Jobs"); /* Create a new problem */ readData(); /* Read in the data */ jobsModel(); /* Basic problem definition */ } } }
Error handling
If an error occurs, BCL .NET does not behaves like the C interface; that is, it prints an error message and but will throw an exception rather than terminate the program. Alternatively, if BCL error handling is disabled by calling XPRBprob.setErrCtrl(0), then all error messages are sent to the user-defined error callback without terminating the program; the user can both check these error messages and the return codes of each method to verify if it has completed correctly. The only case where a BCLExceptions is raised is when an error occurs while constructing a BCLexpr object. Below we show a .NET implementation of the example of user error handling with BCL from Section User error handling. Other features demonstrated by this example include
- redirection of the BCL output stream for the whole program and for an individual problem;
- setting the BCL message printing level;
using System; using BCL; namespace Examples { public class UGExpl3 { public static int rtsbefore = 1; public void modexpl3(ref XPRBprob prob) { XPRBvar[] x = new XPRBvar[3]; XPRBctr[] ctr = new XPRBctr[2]; XPRBexpr cobj; int i; for(i=0;i<2;i++) x[i] = prob.newVar("x_"+i, BCLconstant.XPRB_UI, 0, 100); /* Create the constraints: C1: 2x0 + 3x1 >= 41 C2: x0 + 2x1 = 13 */ XPRBexpr C1linexp = new XPRBexpr(); XPRBexpr C2linexp = new XPRBexpr(); C1linexp = 2 * x[0] + 3 * x[1]; C2linexp = x[0] + 2 * x[1]; prob.newCtr("C1", C1linexp >= 41); prob.newCtr("C2", C2linexp == 13); /* Uncomment the following line to cause an error in the model that triggers the user error handling: */ // x[3] = prob.newVar("x_2", BCLconstant.XPRB_UI, 10, 1); /* Objective: minimize x0+x1 */ cobj = new XPRBexpr(0); for(i=0;i<2;i++) cobj += x[i]; prob.setObj(prob.newCtr("OBJ", cobj)); prob.setSense(BCLconstant.XPRB_MINIM); /* Set objective sense to minimization */ prob.print(); /* Print current problem definition */ prob.lpOptimize(); /* Solve the LP */ prob.printF("Problem status: " + prob.getProbStat() + " LP status: " + prob.getLPStat() + " MIP status: " + prob.getMIPStat() + "\n"); /* This problem is infeasible, that means the following command will fail. * It prints a warning if the message level is at least 2 */ prob.printF("Objective: " + prob.getObjVal() + "\n"); /* Print solution values */ for(i=0;i<2;i++) prob.printF(x[i].getName() + ":" + x[i].getSol() + ", "); prob.printF("\n"); } /***********************************************************************/ /**** User error handling function ****/ public static void usererror(IntPtr prob, object vp, int num, int type, string t) { Exception eBCL = new Exception("Error in usererror()."); System.Console.WriteLine("BCL error " +num+ ": " + t); if(type==BCLconstant.XPRB_ERR) throw eBCL; } /**** User printing function ****/ public static void userprint(IntPtr prob, object vp, string msg) { /* Print 'BCL output' whenever a new output line starts, otherwise continue to print the current line. */ if(rtsbefore==1) System.Console.Write("BCL output: " + msg); else System.Console.Write(msg); rtsbefore = (msg.Length>0 && msg[msg.Length-1]=='\n') ? 1 : 0; } /***********************************************************************/ // This is where one might add custom logging static void DoSomeErrorLogging(string msg) { Console.WriteLine("Here's an error message! {0}", msg); } public static int Main() { try { /* Switch to error handling by the user's program */ XPRB.setErrCtrl(0); // no auto quit on error int initCode = XPRB.init(); if (initCode != 0 && initCode != 32) // both values are valid { DoSomeErrorLogging(Optimizer.XPRS.GetLicErrMsg()); return initCode; } UGExpl3 TestInstance = new UGExpl3(); XPRBprob prob = new XPRBprob("EXPL3"); if (!prob.isValid()) { DoSomeErrorLogging("Unable to create XPRBprob \"EXPL3\""); return 1; } /* Set the printing flag. Try other values: 0 - no printed output, 1 - only errors, 2 - errors and warnings, 3 - all messages */ prob.setMsgLevel(2); /* Define the printing callback function */ prob.MessageCallbacks += new XPRBMessageCallback(userprint); try { prob.ErrorCallbacks += new XPRBErrorCallback(usererror); TestInstance.modexpl3(ref prob); /* Formulate and solve the problem */ System.Console.WriteLine("I'm about to exit cleanly"); return 0; } catch { System.Console.WriteLine("I cannot build the problem"); return 1; } } catch { System.Console.WriteLine("I cannot create the problem"); return 1; } } } }
.NET class reference
The complete set of classes of the BCL .NET interface is summarized in the following list. For a detailed documentation of the .NET interface the reader is referred to the BCL .NET online documentation.
- XPRB
- Initialization and general settings, definition of all parameters.
- XPRBprob
- Problem definition, including methods for creating and deleting the modeling objects, problem solving, changing settings, and retrieving solution information.
- XPRBvar
- Methods for modifying and accessing variables.
- XPRBctr
- Methods for constructing, modifying and accessing constraints.
- XPRBcut
- Methods for constructing, modifying and accessing cuts.
- XPRBsol
- Methods for constructing, modifying and accessing solutions.
- XPRBsos
- Methods for constructing, modifying and accessing Special Ordered Sets.
- XPRBindexSet
- Methods for constructing and accessing index sets and accessing set elements.
- XPRBbasis
- Methods for accessing bases.
- XPRBexpr
- Methods for constructing linear and quadratic expressions.
- XPRBrelation
- Methods for constructing linear or quadratic relations from expressions (extends XPRBexpr).
- XPRBterm
- Methods for initialisation and handling of XPRBterm objects.
- XPRBVersion
- Version number in its encoded form.
- BCLconstant
- All BCL related constants.
- BCLExceptions
- Methods for BCL Exceptions.
- Scanner
- Class for reading file data in to objects in a similar manner to scanf().
All .NET classes that have a direct correspondence with modeling objects in BCL (namely XPRBprob, XPRBvar, XPRBctr, XPRBcut, XPRBsol, XPRBsos, XPRBindexSet, XPRBbasis) take the same names, with the exception of XPRBindexSet. It is possible to obtain the Xpress Optimizer problem corresponding to a BCL .NET problem by using method getXPRSprob of class XPRBprob, please see Section Using the Optimizer with BCL .NET for further detail on using BCL with the Optimizer library.
Most of the methods of the classes with direct correspondence with C modeling objects call standard BCL C functions and return the same result codes.
Two important classes that do not correspond to any standard BCL modeling object is class XPRB that contains methods relating to the initialization and the general status of the software and class BCLconstant that contains the definition of all constant parameters. This means, any parameter with the prefix XPRB_ in standard BCL is referred to as a constant member of the .NET class BCLconstant. For example, XPRB_BV in standard BCL becomes BCLconstant.XPRB_BV in .NET.
In .NET, some additional classes have been introduced to aid the termwise definition of constraints. Linear and quadratic expressions (class XPRBexpr) are required in the definition of constraints and Special Ordered Sets. Linear or quadratic relations (class XPRBrelation), may be used as an intermediary in the definition of constraints.
A couple of other additional classes are related to error handling and data input, namely BCLExceptions which represent exceptions raised when errors occur while building constraints; and Scanner which is used internally to implement the XPRBprob.XPRBreadline and XPRBprob.XPRBreadarrline methods.
© 2001-2025 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.