SdpjParameters.java

/**
 * Copyright (C) 2021 MKLab.org (Koga Laboratory)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mklab.sdpj.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.regex.Pattern;

import org.mklab.sdpj.algorithm.ParameterType;
import org.mklab.sdpj.convert.Sedumi;
import org.mklab.sdpj.iocenter.IO;
import org.mklab.sdpj.tool.Tools;

/**
 * Parameters of SDPJ.
 * 
 * @author koga
 * @version $Revision$, 2021/10/04
 */
public class SdpjParameters {
  /** */
  int precision;
  /** */
  String dataType;
  /** */
  boolean isInitFile;
  /** */
  boolean isInitSparse;
  /** */
  boolean isDataSparse;
  /** */
  boolean isParameter;
  /** */
  String dataFile;
  /** */
  String initFile;
  /** */
  String outFile;
  /** */
  String paraFile;
  /** */
  String kappaString;
  
  ParameterType parameterType;
  
  PrintStream fpOut;
  
  /**
   * Creates {@link SdpjParameters}.
   */
  public SdpjParameters() {
    this.isInitFile = false;
    this.isInitSparse = false;
    this.isDataSparse = false;
    this.isParameter = false;
    this.dataFile = null;
    this.initFile = null;
    this.outFile = null;
    this.paraFile = null;
    this.kappaString = null;
    this.dataType = ""; //$NON-NLS-1$
    this.precision = 77;
  }

  /**
   * This is a solver of SdpjMain, which can compute a optimal solution of SDP problem, is an entry of solving a SDP problem.
   * 
   * @param arguments a command line string
   * @throws IOException If I/O exception occurs
   */
  public void parseArguments(String[] arguments) throws IOException {
    if (arguments.length < 2) {
      showMessage("sdpj"); //$NON-NLS-1$
      System.exit(0);
    }

    if (arguments[0].toLowerCase().equals("sedumi")) { //$NON-NLS-1$
      sedumi(arguments);
    }

    int argc = arguments.length;

    StringBuffer buff = new StringBuffer();
    buff.append("SdpjMain start at " + new java.util.Date() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$

    this.parameterType = ParameterType.PARAMETER_DEFAULT;

    if (argc == 1) {
      showMessage(arguments[0]);
    }

    if (arguments[0].charAt(0) == '-') {
      for (int i = 0; i < argc; ++i) {
        String target = arguments[i];
        if (target.equals("-dd") && i + 1 < argc) { //$NON-NLS-1$
          this.dataFile = arguments[i + 1];
          i++;
          continue;
        }
        if (target.equals("-ds") && i + 1 < argc) { //$NON-NLS-1$
          this.dataFile = arguments[i + 1];
          i++;
          this.isDataSparse = true;
          continue;
        }
        if (target.equals("-id") && i + 1 < argc) { //$NON-NLS-1$
          this.initFile = arguments[i + 1];
          i++;
          this.isInitFile = true;
          continue;
        }
        if (target.equals("-is") && i + 1 < argc) { //$NON-NLS-1$
          this.initFile = arguments[i + 1];
          i++;
          this.isInitFile = true;
          this.isInitSparse = true;
          continue;
        }
        if (target.equals("-o") && i + 1 < argc) { //$NON-NLS-1$
          this.outFile = arguments[i + 1];
          i++;
          continue;
        }
        if (target.equals("-p") && i + 1 < argc) { //$NON-NLS-1$
          this.paraFile = arguments[i + 1];
          i++;
          this.isParameter = true;
          continue;
        }
        if (target.equals("-k") && i + 1 < argc) { //$NON-NLS-1$
          this.kappaString = arguments[i + 1];
          Tools.message("Kappa = " + this.kappaString); //$NON-NLS-1$
          i++;
          continue;
        }

        if (target.equals("-pt") && i + 1 < argc) { //$NON-NLS-1$
          int tmp = atoi(arguments[i + 1]);
          switch (tmp) {
            case 0:
              this.parameterType = ParameterType.PARAMETER_DEFAULT;
              break;
            case 1:
              this.parameterType = ParameterType.PARAMETER_AGGRESSIVE;
              break;
            case 2:
              this.parameterType = ParameterType.PARAMETER_STABLE;
              break;
            case 3:
              this.parameterType = ParameterType.PARAMETER_MP115_DEFAULT;
              break;
            case 4:
              this.parameterType = ParameterType.PARAMETER_MP115_STABLE;
              break;
            default:
              this.parameterType = ParameterType.PARAMETER_DEFAULT;
          }
          if (tmp == 3 || tmp == 4) {
            this.dataType = "mpfloat"; //$NON-NLS-1$
          } else {
            this.dataType = "double"; //$NON-NLS-1$
          }
          i++;
          this.paraFile = null;
          this.isParameter = false;
          continue;
        }
      }
    } else { // SDPA argument
      this.dataFile = arguments[0];
      int len = this.dataFile.length();
      if (this.dataFile.charAt(len - 1) == 's' && this.dataFile.charAt(len - 2) == '-') {
        this.isDataSparse = true;
      }

      this.outFile = arguments[1];

      for (int i = 2; i < arguments.length; ++i) {
        if (arguments[i].equals("-pt") && i + 1 < argc) { //$NON-NLS-1$
          int tmp = atoi(arguments[i + 1]);
          switch (tmp) {
            case 0:
              this.parameterType = ParameterType.PARAMETER_DEFAULT;
              break;
            case 1:
              this.parameterType = ParameterType.PARAMETER_AGGRESSIVE;
              break;
            case 2:
              this.parameterType = ParameterType.PARAMETER_STABLE;
              break;
            case 3:
              this.parameterType = ParameterType.PARAMETER_MP115_DEFAULT;
              break;
            case 4:
              this.parameterType = ParameterType.PARAMETER_MP115_STABLE;
              break;
            default:
              this.parameterType = ParameterType.PARAMETER_DEFAULT;
          }
          if (tmp == 3 || tmp == 4) {
            this.dataType = "mpfloat"; //$NON-NLS-1$
          } else {
            this.dataType = "double"; //$NON-NLS-1$
          }
          i++;
          this.paraFile = null;
          this.isParameter = false;
        } else {
          if (arguments[i].equals("-p") && i + 1 < argc) { //$NON-NLS-1$
            this.paraFile = arguments[i + 1];
            i++;
            this.isParameter = true;
          } else {
            this.initFile = arguments[i];
            this.isInitFile = true;
            len = this.initFile.length();
            if (this.initFile.charAt(len - 1) == 's' && this.initFile.charAt(len - 2) == '-') {
              this.isInitSparse = true;
            }
          }
        }
      }
    }

    if (this.dataType.equals("")) { //$NON-NLS-1$
      if (this.paraFile == null) {
        this.paraFile = "param/param.sdpj"; //$NON-NLS-1$
      }
      creatDataType(this.paraFile);
      this.isParameter = true;
    }

    if (this.dataFile == null || this.outFile == null) {
      showMessage(arguments[0]);
    }

    buff.append("data      is " + this.dataFile); //$NON-NLS-1$
    if (this.isDataSparse) {
      buff.append(" : sparse\n"); //$NON-NLS-1$
    } else {
      buff.append(" : dense\n"); //$NON-NLS-1$
    }
    if (this.paraFile != null) {
      buff.append("parameter is " + this.paraFile + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
    }
    if (this.outFile != null) {
      buff.append("out       is " + this.outFile + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
    }
    if (this.initFile != null) {
      buff.append("initial   is " + this.initFile); //$NON-NLS-1$
    }
    if (this.isInitFile) {
      if (this.isInitSparse) {
        buff.append(" : sparse\n"); //$NON-NLS-1$
      } else {
        buff.append(" : dense\n"); //$NON-NLS-1$
      }
    } else {
      buff.append("\n"); //$NON-NLS-1$
    }
    
    if (this.paraFile == null) {
      if (this.parameterType == ParameterType.PARAMETER_DEFAULT) {
        buff.append("set       is Double DEFAULT\n");// << endl; //$NON-NLS-1$
      } else if (this.parameterType == ParameterType.PARAMETER_AGGRESSIVE) {
        buff.append("set       is Double AGGRESSIVE\n");// << endl; //$NON-NLS-1$
      } else if (this.parameterType == ParameterType.PARAMETER_STABLE) {
        buff.append("set       is Double STABLE\n");// << endl; //$NON-NLS-1$
      } else if (this.parameterType == ParameterType.PARAMETER_MP115_DEFAULT) {
        buff.append("set       is MPFloat DEFAULT\n");// << endl; //$NON-NLS-1$
      } else if (this.parameterType == ParameterType.PARAMETER_MP115_STABLE) {
        buff.append("set       is MPFloat STABLE\n");// << endl; //$NON-NLS-1$
      }
    }
    
    Tools.message(buff.toString(), System.out);
    
    try (PrintStream fp  = new PrintStream(new FileOutputStream(new File(this.outFile)))) {
      this.fpOut = fp;
      Tools.message(buff.toString(), fp);
    }
  }

  /**
   * the purpose of this method is show how to use <code>SdpjMain</code> when users make a mistake in using <code>SdpjMain</code>.
   * 
   * @param argv0 a string, which is "<code>sdpj</code>"
   */
  private void showMessage(String argv0) {
    StringBuffer buff = new StringBuffer();
    buff.append("\n"); //$NON-NLS-1$
    buff.append("************* Please assign data file and output file.*************\n"); //$NON-NLS-1$
    buff.append("\n"); //$NON-NLS-1$
    buff.append("------------------- option type 1 --------------------\n"); //$NON-NLS-1$
    buff.append(argv0 + " DataFile OutputFile [InitialPtFile]" + " [-pt parameters] [-p InitialParamFile]\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("parameters = 0 default, 1 aggressive," + " 2 stable, 3 MPFloat default, 4 MPFlaot stable\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("example1-1: " + argv0 + " example.dat example.result\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("example1-2: " + argv0 + " example.dat-s example.result\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("example1-3: " + argv0 + " example.dat example.result example.ini\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("example1-4: " + argv0 + " example.dat example.result -pt 2\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("example1-5: " + argv0 + " example.dat example.result -p param.sdpj\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("\n"); //$NON-NLS-1$

    buff.append("------------------- option type 2 --------------------\n"); //$NON-NLS-1$
    buff.append(argv0 + " [option filename]+ \n"); //$NON-NLS-1$
    buff.append("  -dd : data dense :: -ds : data sparse     \n"); //$NON-NLS-1$
    buff.append("  -id : init dense :: -is : init sparse     \n"); //$NON-NLS-1$
    buff.append("  -o  : output     :: -p  : parameter       \n"); //$NON-NLS-1$
    buff.append("  -pt : parameters , 0 default, 1 aggressive, 2 stable,\n"); //$NON-NLS-1$
    buff.append("                   3 MPFloat default, 4 MPFlaot stable\n"); //$NON-NLS-1$
    buff.append("example2-1: " + argv0 + " -o example.result -dd example.dat\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buff.append("example2-2: " + argv0 + " -ds example.dat-s -o example.result " + "-p param.sdpj\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    buff.append("example2-3: " + argv0 + " -ds example.dat-s -o example.result " + "-pt 2\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

    buff.append("\nNote:if you run program by command line mode,please refer to option type1\n"); //$NON-NLS-1$
    buff.append("or type2.Otherwise,you must give parameter args some information about \n"); //$NON-NLS-1$
    buff.append("DataFile & OutputFile,etc.If you do not input the InitialParamFile in Type1,\n"); //$NON-NLS-1$
    buff.append("the datatype will be used by default. And Type2 is also the same. Otherwise,\n"); //$NON-NLS-1$
    buff.append("the datatype is appointed based on the InitialParamFile. To run the program \n"); //$NON-NLS-1$
    buff.append("correctly,you must appoint datafile and outputfile.\n"); //$NON-NLS-1$
    buff.append("For example: args[0] = \"example.dat-s\", args[1] = \"example.result\"\n"); //$NON-NLS-1$
    buff.append("Recommedation:if you want to appoint a datatype, input a InitialParamFile.\n"); //$NON-NLS-1$
    buff.append("\n******************************************************************\n"); //$NON-NLS-1$

    System.out.println(new String(buff));
  }

  /**
   * The program is to change an inputting style of solver <code>sedumi</code> into the style of solver <code>sdpj</code>
   * 
   * @param arguments a command line string
   */
  private void sedumi(String[] arguments) {
    final Sedumi sedumi = new Sedumi();
    final String writeFile = arguments[1];
    final String fileFormat = writeFile.substring(writeFile.lastIndexOf(".")); //$NON-NLS-1$
    final String datsFile = writeFile.replaceAll(Pattern.quote(fileFormat), ".dat-s"); //$NON-NLS-1$ 

    System.out.println(datsFile + " is building..."); //$NON-NLS-1$
    sedumi.SedumiLoad(writeFile, datsFile);
    System.out.println("Done"); //$NON-NLS //$NON-NLS-1$
    System.out.println("please input command:% sdpj " + datsFile + " outputfile" + " [options]\n"); //$NON-NLS //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    System.exit(0);
  }

  /**
   * Create a specific data type from a parameter file. Users can freely specify a data type, which can be used in <code>SdpjMain</code>.
   *
   * @param parameterFile Parameter file
   * @throws FileNotFoundException If no file found exception occurs
   * @throws IOException If I/O exception occurs
   */
  private void creatDataType(String parameterFile) throws FileNotFoundException, IOException {
    BufferedReader reader = new BufferedReader(new FileReader(new File(parameterFile)));

    while (this.dataType.equals("double") == false && this.dataType.equals("mpfloat") == false) { //$NON-NLS-1$//$NON-NLS-2$
      String string = reader.readLine();
      if (string == null) {
        break;
      }

      String type = string.toLowerCase();
      if (type.contains("double")) { //$NON-NLS-1$
        this.dataType = "double"; //$NON-NLS-1$
      } else if (type.contains("mpfloat")) { //$NON-NLS-1$
        this.dataType = "mpfloat"; //$NON-NLS-1$
      } else {
        this.dataType = type;
      }
    }

    if (this.dataType.equals("mpfloat")) { //$NON-NLS-1$
      this.precision = IO.readInteger(reader.readLine());
    }
  }

  /**
   * This program is to change a <code>string</code> into a <code>integer</code>, which can only change a <code>char</code> of the string. And if the char is not a <code>number</code> char, maybe it
   * occurs an exception. <br>文字列の最初に数字きたらその整数を返します。 本来は文字列の先頭から走査していき、数字以外が来たらそこまでの整数を返すというもの。 今回は引数の判別で、一桁のものしかないため、最初の数字しか考えていない。
   * 
   * @param str a integer string
   * @return a integer
   */
  private int atoi(String str) {

    switch (str.charAt(0)) {
      case '0':
        return 0;
      case '1':
        return 1;
      case '2':
        return 2;
      case '3':
        return 3;
      case '4':
        return 4;
      case '5':
        return 5;
      case '6':
        return 6;
      case '7':
        return 7;
      case '8':
        return 8;
      case '9':
        return 9;
      case '-':
        return -atoi(str.substring(1));
    }
    return 0;
  }

  /**
   * Returns string of kappa.
   * 
   * @return string of kappa
   */
  public String getKappaString() {
    return this.kappaString;
  }
}