1    	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2    	/*                                                                           */
3    	/*                  This file is part of the program and library             */
4    	/*         SCIP --- Solving Constraint Integer Programs                      */
5    	/*                                                                           */
6    	/*  Copyright (c) 2002-2023 Zuse Institute Berlin (ZIB)                      */
7    	/*                                                                           */
8    	/*  Licensed under the Apache License, Version 2.0 (the "License");          */
9    	/*  you may not use this file except in compliance with the License.         */
10   	/*  You may obtain a copy of the License at                                  */
11   	/*                                                                           */
12   	/*      http://www.apache.org/licenses/LICENSE-2.0                           */
13   	/*                                                                           */
14   	/*  Unless required by applicable law or agreed to in writing, software      */
15   	/*  distributed under the License is distributed on an "AS IS" BASIS,        */
16   	/*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
17   	/*  See the License for the specific language governing permissions and      */
18   	/*  limitations under the License.                                           */
19   	/*                                                                           */
20   	/*  You should have received a copy of the Apache-2.0 license                */
21   	/*  along with SCIP; see the file LICENSE. If not visit scipopt.org.         */
22   	/*                                                                           */
23   	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
24   	
25   	/**@file   scipshell.c
26   	 * @ingroup OTHER_CFILES
27   	 * @brief  SCIP command line interface
28   	 * @author Tobias Achterberg
29   	 */
30   	
31   	/*--+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0----+----1----+----2*/
32   	
33   	#include <stdio.h>
34   	#include <string.h>
35   	#include <ctype.h>
36   	
37   	#include "scip/scip.h"
38   	#include "scip/scipdefplugins.h"
39   	#include "scip/scipshell.h"
40   	#include "scip/message_default.h"
41   	#include "scip/reader_nl.h"
42   	
43   	/*
44   	 * Message Handler
45   	 */
46   	
47   	static
48   	SCIP_RETCODE readParams(
49   	   SCIP*                 scip,               /**< SCIP data structure */
50   	   const char*           filename            /**< parameter file name */
51   	   )
52   	{
53   	   if( SCIPfileExists(filename) )
54   	   {
55   	      SCIPinfoMessage(scip, NULL, "reading user parameter file <%s>\n", filename);
56   	      SCIP_CALL( SCIPreadParams(scip, filename) );
57   	   }
58   	   else
59   	      SCIPinfoMessage(scip, NULL, "user parameter file <%s> not found - using default parameters\n", filename);
60   	
61   	   return SCIP_OKAY;
62   	}
63   	
64   	static
65   	SCIP_RETCODE fromCommandLine(
66   	   SCIP*                 scip,               /**< SCIP data structure */
67   	   const char*           filename            /**< input file name */
68   	   )
69   	{
70   	   SCIP_RETCODE retcode;
71   	   SCIP_Bool outputorigsol = FALSE;
72   	
73   	   /********************
74   	    * Problem Creation *
75   	    ********************/
76   	
77   	   /** @note The message handler should be only fed line by line such the message has the chance to add string in front
78   	    *        of each message
79   	    */
80   	   SCIPinfoMessage(scip, NULL, "\n");
81   	   SCIPinfoMessage(scip, NULL, "read problem <%s>\n", filename);
82   	   SCIPinfoMessage(scip, NULL, "============\n");
83   	   SCIPinfoMessage(scip, NULL, "\n");
84   	
85   	   retcode = SCIPreadProb(scip, filename, NULL);
86   	
87   	   switch( retcode )
88   	   {
89   	   case SCIP_NOFILE:
90   	      SCIPinfoMessage(scip, NULL, "file <%s> not found\n", filename);
91   	      return SCIP_OKAY;
92   	   case SCIP_PLUGINNOTFOUND:
93   	      SCIPinfoMessage(scip, NULL, "no reader for input file <%s> available\n", filename);
94   	      return SCIP_OKAY;
95   	   case SCIP_READERROR:
96   	      SCIPinfoMessage(scip, NULL, "error reading file <%s>\n", filename);
97   	      return SCIP_OKAY;
98   	   default:
99   	      SCIP_CALL( retcode );
100  	   } /*lint !e788*/
101  	
102  	   /*******************
103  	    * Problem Solving *
104  	    *******************/
105  	
106  	   /* solve problem */
107  	   SCIPinfoMessage(scip, NULL, "\nsolve problem\n");
108  	   SCIPinfoMessage(scip, NULL, "=============\n\n");
109  	
110  	   SCIP_CALL( SCIPsolve(scip) );
111  	
112  	   /*******************
113  	    * Solution Output *
114  	    *******************/
115  	
116  	   SCIP_CALL( SCIPgetBoolParam(scip, "misc/outputorigsol", &outputorigsol) );
117  	   if ( outputorigsol )
118  	   {
119  	      SCIP_SOL* bestsol;
120  	
121  	      SCIPinfoMessage(scip, NULL, "\nprimal solution (original space):\n");
122  	      SCIPinfoMessage(scip, NULL, "=================================\n\n");
123  	
124  	      bestsol = SCIPgetBestSol(scip);
125  	      if ( bestsol == NULL )
126  	         SCIPinfoMessage(scip, NULL, "no solution available\n");
127  	      else
128  	      {
129  	         SCIP_SOL* origsol;
130  	
131  	         SCIP_CALL( SCIPcreateSolCopy(scip, &origsol, bestsol) );
132  	         SCIP_CALL( SCIPretransformSol(scip, origsol) );
133  	         SCIP_CALL( SCIPprintSol(scip, origsol, NULL, FALSE) );
134  	         SCIP_CALL( SCIPfreeSol(scip, &origsol) );
135  	      }
136  	   }
137  	   else
138  	   {
139  	      SCIPinfoMessage(scip, NULL, "\nprimal solution (transformed space):\n");
140  	      SCIPinfoMessage(scip, NULL, "====================================\n\n");
141  	
142  	      SCIP_CALL( SCIPprintBestSol(scip, NULL, FALSE) );
143  	   }
144  	
145  	   /**************
146  	    * Statistics *
147  	    **************/
148  	
149  	   SCIPinfoMessage(scip, NULL, "\nStatistics\n");
150  	   SCIPinfoMessage(scip, NULL, "==========\n\n");
151  	
152  	   SCIP_CALL( SCIPprintStatistics(scip, NULL) );
153  	
154  	   return SCIP_OKAY;
155  	}
156  	
157  	/** runs SCIP as if it was called by AMPL */
158  	static
159  	SCIP_RETCODE fromAmpl(
160  	   SCIP*                 scip,               /**< SCIP data structure */
161  	   char*                 nlfilename,         /**< name of .nl file, without the .nl */
162  	   SCIP_Bool             interactive,        /**< whether to start SCIP shell instead of solve only */
163  	   const char*           defaultsetname      /**< name of default settings file */
164  	   )
165  	{
166  	#ifdef SCIP_WITH_AMPL
167  	   char fullnlfilename[SCIP_MAXSTRLEN];
168  	   char* logfile;
169  	   SCIP_Bool printstat;
170  	   char* options;
171  	   size_t nlfilenamelen;
172  	
173  	   SCIP_CALL( SCIPaddBoolParam(scip, "display/statistics",
174  	      "whether to print statistics on a solve",
175  	      &printstat, FALSE, FALSE, NULL, NULL) );
176  	
177  	   SCIP_CALL( SCIPaddStringParam(scip, "display/logfile",
178  	      "name of file to write SCIP log to (additionally to writing to stdout)",
179  	      NULL, FALSE, "", NULL, NULL) );
180  	
181  	   SCIPprintVersion(scip, NULL);
182  	   SCIPinfoMessage(scip, NULL, "\n");
183  	
184  	   SCIPprintExternalCodes(scip, NULL);
185  	   SCIPinfoMessage(scip, NULL, "\n");
186  	
187  	   options = getenv("scip_options");
188  	   if( options != NULL )
189  	   {
190  	      /* parse and apply options from scip_options env variable */
191  	      char* optname;
192  	      char* optval;
193  	
194  	      SCIPverbMessage(scip, SCIP_VERBLEVEL_HIGH, NULL, "applying scip_options:\n");
195  	
196  	      optname = strtok(options, " ");
197  	      optval = strtok(NULL, " ");
198  	      while( optname != NULL && optval != NULL )
199  	      {
200  	         SCIPverbMessage(scip, SCIP_VERBLEVEL_HIGH, NULL, "  %s = %s\n", optname, optval);
201  	         SCIP_CALL( SCIPsetParam(scip, optname, optval) );
202  	
203  	         optname = strtok(NULL, " ");
204  	         optval = strtok(NULL, " ");
205  	      }
206  	   }
207  	
208  	   if( defaultsetname != NULL )
209  	   {
210  	      if( SCIPfileExists(defaultsetname) )
211  	      {
212  	         SCIPinfoMessage(scip, NULL, "reading user parameter file <%s>\n", defaultsetname);
213  	         SCIPinfoMessage(scip, NULL, "===========================\n\n");
214  	         SCIP_CALL( SCIPreadParams(scip, defaultsetname) );
215  	         SCIP_CALL( SCIPwriteParams(scip, NULL, FALSE, TRUE) );
216  	         SCIPinfoMessage(scip, NULL, "\n");
217  	      }
218  	      else
219  	      {
220  	         SCIPinfoMessage(scip, NULL, "user parameter file <%s> not found - using default parameters\n", defaultsetname);
221  	      }
222  	   }
223  	
224  	   SCIP_CALL( SCIPgetStringParam(scip, "display/logfile", &logfile) );
225  	   if( *logfile )
226  	      SCIPsetMessagehdlrLogfile(scip, logfile);
227  	
228  	   /* AMPL calls solver with file without .nl extension, but others (Pyomo) may not
229  	    * so add .nl only if not already present
230  	    */
231  	   nlfilenamelen = strlen(nlfilename);
232  	   if( nlfilenamelen > 3 && strcmp(nlfilename + (nlfilenamelen-3), ".nl") == 0 )
233  	      (void) SCIPsnprintf(fullnlfilename, SCIP_MAXSTRLEN, "%s", nlfilename);
234  	   else
235  	      (void) SCIPsnprintf(fullnlfilename, SCIP_MAXSTRLEN, "%s.nl", nlfilename);
236  	   SCIPinfoMessage(scip, NULL, "read problem <%s>\n", fullnlfilename);
237  	   SCIPinfoMessage(scip, NULL, "============\n\n");
238  	
239  	   SCIP_CALL( SCIPreadProb(scip, fullnlfilename, "nl") );
240  	
241  	   if( interactive )
242  	   {
243  	      SCIP_CALL( SCIPstartInteraction(scip) );
244  	   }
245  	   else
246  	   {
247  	      SCIPinfoMessage(scip, NULL, "\nsolve problem\n");
248  	      SCIPinfoMessage(scip, NULL, "=============\n\n");
249  	
250  	      SCIP_CALL( SCIPsolve(scip) );
251  	   }
252  	
253  	   SCIP_CALL( SCIPgetBoolParam(scip, "display/statistics", &printstat) );
254  	   if( printstat )
255  	   {
256  	      SCIPinfoMessage(scip, NULL, "\nStatistics\n");
257  	      SCIPinfoMessage(scip, NULL, "==========\n\n");
258  	
259  	      SCIP_CALL( SCIPprintStatistics(scip, NULL) );
260  	   }
261  	
262  	   SCIP_CALL( SCIPwriteSolutionNl(scip) );
263  	
264  	   return SCIP_OKAY;
265  	
266  	#else /* SCIP_WITH_AMPL */
267  	   SCIPerrorMessage("SCIP has been compiled without AMPL support.\n");
268  	   return SCIP_PLUGINNOTFOUND;
269  	#endif
270  	}
271  	
272  	/** evaluates command line parameters and runs SCIP appropriately in the given SCIP instance */
273  	SCIP_RETCODE SCIPprocessShellArguments(
274  	   SCIP*                 scip,               /**< SCIP data structure */
275  	   int                   argc,               /**< number of shell parameters */
276  	   char**                argv,               /**< array with shell parameters */
277  	   const char*           defaultsetname      /**< name of default settings file */
278  	   )
279  	{  /*lint --e{850}*/
280  	   char* probname = NULL;
281  	   char* settingsname = NULL;
282  	   char* logname = NULL;
283  	   int randomseed;
284  	   SCIP_Bool randomseedread;
285  	   SCIP_Bool quiet;
286  	   SCIP_Bool paramerror;
287  	   SCIP_Bool interactive;
288  	   SCIP_Bool onlyversion;
289  	   SCIP_Real primalreference = SCIP_UNKNOWN;
290  	   SCIP_Real dualreference = SCIP_UNKNOWN;
291  	   const char* dualrefstring;
292  	   const char* primalrefstring;
293  	   int i;
294  	
295  	   /********************
296  	    * Parse parameters *
297  	    ********************/
298  	
299  	   /* recognize and handle case where we were called from AMPL first */
300  	   if( argc >= 3 && strcmp(argv[2], "-AMPL") == 0 )
301  	   {
302  	      /* check for optional argument -i after -AMPL */
303  	      interactive = argc >= 4 && strcmp(argv[3], "-i") == 0;
304  	
305  	      SCIP_CALL( fromAmpl(scip, argv[1], interactive, defaultsetname) );
306  	
307  	      return SCIP_OKAY;
308  	   }
309  	
310  	   quiet = FALSE;
311  	   paramerror = FALSE;
312  	   interactive = FALSE;
313  	   onlyversion = FALSE;
314  	   randomseedread = FALSE;
315  	   randomseed = 0;
316  	   primalrefstring = NULL;
317  	   dualrefstring = NULL;
318  	
319  	   for( i = 1; i < argc; ++i )
320  	   {
321  	      if( strcmp(argv[i], "-l") == 0 )
322  	      {
323  	         i++;
324  	         if( i < argc )
325  	            logname = argv[i];
326  	         else
327  	         {
328  	            printf("missing log filename after parameter '-l'\n");
329  	            paramerror = TRUE;
330  	         }
331  	      }
332  	      else if( strcmp(argv[i], "-q") == 0 )
333  	         quiet = TRUE;
334  	      else if( strcmp(argv[i], "-v") == 0 )
335  	         onlyversion = TRUE;
336  	      else if( strcmp(argv[i], "--version") == 0 )
337  	         onlyversion = TRUE;
338  	      else if( strcmp(argv[i], "-s") == 0 )
339  	      {
340  	         i++;
341  	         if( i < argc )
342  	            settingsname = argv[i];
343  	         else
344  	         {
345  	            printf("missing settings filename after parameter '-s'\n");
346  	            paramerror = TRUE;
347  	         }
348  	      }
349  	      else if( strcmp(argv[i], "-f") == 0 )
350  	      {
351  	         i++;
352  	         if( i < argc )
353  	            probname = argv[i];
354  	         else
355  	         {
356  	            printf("missing problem filename after parameter '-f'\n");
357  	            paramerror = TRUE;
358  	         }
359  	      }
360  	      else if( strcmp(argv[i], "-c") == 0 )
361  	      {
362  	         i++;
363  	         if( i < argc )
364  	         {
365  	            SCIP_CALL( SCIPaddDialogInputLine(scip, argv[i]) );
366  	            interactive = TRUE;
367  	         }
368  	         else
369  	         {
370  	            printf("missing command line after parameter '-c'\n");
371  	            paramerror = TRUE;
372  	         }
373  	      }
374  	      else if( strcmp(argv[i], "-b") == 0 )
375  	      {
376  	         i++;
377  	         if( i < argc )
378  	         {
379  	            SCIP_FILE* file;
380  	
381  	            file = SCIPfopen(argv[i], "r");
382  	            if( file == NULL )
383  	            {
384  	               printf("cannot read command batch file <%s>\n", argv[i]);
385  	               SCIPprintSysError(argv[i]);
386  	               paramerror = TRUE;
387  	            }
388  	            else
389  	            {
390  	               while( !SCIPfeof(file) )
391  	               {
392  	                  char buffer[SCIP_MAXSTRLEN];
393  	
394  	                  (void)SCIPfgets(buffer, (int) sizeof(buffer), file);
395  	                  if( buffer[0] != '\0' )
396  	                  {
397  	                     SCIP_CALL_FINALLY( SCIPaddDialogInputLine(scip, buffer), SCIPfclose(file) );
398  	                  }
399  	               }
400  	               SCIPfclose(file);
401  	               interactive = TRUE;
402  	            }
403  	         }
404  	         else
405  	         {
406  	            printf("missing command batch filename after parameter '-b'\n");
407  	            paramerror = TRUE;
408  	         }
409  	      }
410  	      else if( strcmp(argv[i], "-r") == 0 )
411  	      {
412  	         /*read a random seed from the command line */
413  	         i++;
414  	         if( i < argc && isdigit(argv[i][0]) )
415  	         {
416  	            randomseed = atoi(argv[i]);
417  	            randomseedread = TRUE;
418  	         }
419  	         else
420  	         {
421  	            printf("Random seed parameter '-r' followed by something that is not an integer\n");
422  	            paramerror = TRUE;
423  	         }
424  	      }
425  	      else if( strcmp(argv[i], "-o") == 0 )
426  	      {
427  	         if( i >= argc - 2 )
428  	         {
429  	            printf("wrong usage of reference objective parameter '-o': -o <primref> <dualref>\n");
430  	            paramerror = TRUE;
431  	         }
432  	         else
433  	         {
434  	            /* do not parse the strings directly, the settings could still influence the value of +-infinity */
435  	            primalrefstring = argv[i + 1];
436  	            dualrefstring = argv[i+2];
437  	         }
438  	         i += 2;
439  	      }
440  	      else
441  	      {
442  	         printf("invalid parameter <%s>\n", argv[i]);
443  	         paramerror = TRUE;
444  	      }
445  	   }
446  	
447  	   if( interactive && probname != NULL )
448  	   {
449  	      printf("cannot mix batch mode '-c' and '-b' with file mode '-f'\n");
450  	      paramerror = TRUE;
451  	   }
452  	
453  	   if( !paramerror )
454  	   {
455  	      /***********************************
456  	       * create log file message handler *
457  	       ***********************************/
458  	
459  	      if( quiet )
460  	      {
461  	         SCIPsetMessagehdlrQuiet(scip, quiet);
462  	      }
463  	
464  	      if( logname != NULL )
465  	      {
466  	         SCIPsetMessagehdlrLogfile(scip, logname);
467  	      }
468  	
469  	      /***********************************
470  	       * Version and library information *
471  	       ***********************************/
472  	
473  	      SCIPprintVersion(scip, NULL);
474  	      SCIPinfoMessage(scip, NULL, "\n");
475  	
476  	      SCIPprintExternalCodes(scip, NULL);
477  	      SCIPinfoMessage(scip, NULL, "\n");
478  	
479  	      if( onlyversion )
480  	      {
481  	         SCIPprintBuildOptions(scip, NULL);
482  	         SCIPinfoMessage(scip, NULL, "\n");
483  	         return SCIP_OKAY;
484  	      }
485  	
486  	      /*****************
487  	       * Load settings *
488  	       *****************/
489  	
490  	      if( settingsname != NULL )
491  	      {
492  	         SCIP_CALL( readParams(scip, settingsname) );
493  	      }
494  	      else if( defaultsetname != NULL )
495  	      {
496  	         SCIP_CALL( readParams(scip, defaultsetname) );
497  	      }
498  	
499  	      /************************************
500  	       * Change random seed, if specified *
501  	       ***********************************/
502  	      if( randomseedread )
503  	      {
504  	         SCIP_CALL( SCIPsetIntParam(scip, "randomization/randomseedshift", randomseed) );
505  	      }
506  	
507  	      /**************
508  	       * Start SCIP *
509  	       **************/
510  	
511  	      if( probname != NULL )
512  	      {
513  	         SCIP_Bool validatesolve = FALSE;
514  	
515  	         if( primalrefstring != NULL && dualrefstring != NULL )
516  	         {
517  	            char *endptr;
518  	            if( ! SCIPparseReal(scip, primalrefstring, &primalreference, &endptr) ||
519  	                     ! SCIPparseReal(scip, dualrefstring, &dualreference, &endptr) )
520  	            {
521  	               printf("error parsing primal and dual reference values for validation: %s %s\n", primalrefstring, dualrefstring);
522  	               return SCIP_ERROR;
523  	            }
524  	            else
525  	               validatesolve = TRUE;
526  	         }
527  	         SCIP_CALL( fromCommandLine(scip, probname) );
528  	
529  	         /* validate the solve */
530  	         if( validatesolve )
531  	         {
532  	            SCIP_CALL( SCIPvalidateSolve(scip, primalreference, dualreference, SCIPfeastol(scip), FALSE, NULL, NULL, NULL) );
533  	         }
534  	      }
535  	      else
536  	      {
537  	         SCIPinfoMessage(scip, NULL, "\n");
538  	         SCIP_CALL( SCIPstartInteraction(scip) );
539  	      }
540  	   }
541  	   else
542  	   {
543  	      printf("\nsyntax: %s [-l <logfile>] [-q] [-s <settings>] [-r <randseed>] [-f <problem>] [-b <batchfile>] [-c \"command\"]\n"
544  	         "  -v, --version : print version and build options\n"
545  	         "  -l <logfile>  : copy output into log file\n"
546  	         "  -q            : suppress screen messages\n"
547  	         "  -s <settings> : load parameter settings (.set) file\n"
548  	         "  -f <problem>  : load and solve problem file\n"
549  	         "  -o <primref> <dualref> : pass primal and dual objective reference values for validation at the end of the solve\n"
550  	         "  -b <batchfile>: load and execute dialog command batch file (can be used multiple times)\n"
551  	         "  -r <randseed> : nonnegative integer to be used as random seed. "
552  	         "Has priority over random seed specified through parameter settings (.set) file\n"
553  	         "  -c \"command\"  : execute single line of dialog commands (can be used multiple times)\n",
554  	         argv[0]);
555  	#ifdef SCIP_WITH_AMPL
556  	      printf("\nas AMPL solver: %s <.nl-file without the .nl> -AMPL [-i]\n"
557  	         "  -i : start interactive SCIP shell after .nl file has been read\n",
558  	         argv[0]);
559  	#endif
560  	      printf("\n");
561  	   }
562  	
563  	   return SCIP_OKAY;
564  	}
565  	
566  	/** creates a SCIP instance with default plugins, evaluates command line parameters, runs SCIP appropriately,
567  	 *  and frees the SCIP instance
568  	 */
569  	SCIP_RETCODE SCIPrunShell(
570  	   int                   argc,               /**< number of shell parameters */
571  	   char**                argv,               /**< array with shell parameters */
572  	   const char*           defaultsetname      /**< name of default settings file */
573  	   )
574  	{
575  	   SCIP* scip = NULL;
576  	
577  	   /*********
578  	    * Setup *
579  	    *********/
580  	
581  	   /* initialize SCIP */
582  	   SCIP_CALL( SCIPcreate(&scip) );
583  	
584  	   /* we explicitly enable the use of a debug solution for this main SCIP instance */
585  	   SCIPenableDebugSol(scip);
586  	
587  	   /* include default SCIP plugins */
588  	   SCIP_CALL( SCIPincludeDefaultPlugins(scip) );
589  	
590  	   /**********************************
591  	    * Process command line arguments *
592  	    **********************************/
593  	
594  	   SCIP_CALL( SCIPprocessShellArguments(scip, argc, argv, defaultsetname) );
595  	
596  	   /********************
597  	    * Deinitialization *
598  	    ********************/
599  	   SCIP_CALL( SCIPfree(&scip) );
600  	
601  	   BMScheckEmptyMemory();
602  	
603  	   return SCIP_OKAY;
604  	}
605