/** @return */
 public boolean hasFourInputs() {
   return (wtype1 != null && wtype1.hasFourInputs()) || (wtype3 != null && wtype3.hasFourInputs());
 }
  public static QuaternaryInstruction parseInstruction(String str) throws DMLRuntimeException {
    String opcode = InstructionUtils.getOpCode(str);

    // validity check
    if (!InstructionUtils.isDistQuaternaryOpcode(opcode)) {
      throw new DMLRuntimeException("Unexpected opcode in QuaternaryInstruction: " + str);
    }

    // instruction parsing
    if (WeightedSquaredLoss.OPCODE.equalsIgnoreCase(opcode) // wsloss
        || WeightedSquaredLossR.OPCODE.equalsIgnoreCase(opcode)) {
      boolean isRed = WeightedSquaredLossR.OPCODE.equalsIgnoreCase(opcode);

      // check number of fields (4 inputs, output, type)
      if (isRed) InstructionUtils.checkNumFields(str, 8);
      else InstructionUtils.checkNumFields(str, 6);

      // parse instruction parts (without exec type)
      String[] parts = InstructionUtils.getInstructionParts(str);

      byte in1 = Byte.parseByte(parts[1]);
      byte in2 = Byte.parseByte(parts[2]);
      byte in3 = Byte.parseByte(parts[3]);
      byte in4 = Byte.parseByte(parts[4]);
      byte out = Byte.parseByte(parts[5]);
      WeightsType wtype = WeightsType.valueOf(parts[6]);

      // in mappers always through distcache, in reducers through distcache/shuffle
      boolean cacheU = isRed ? Boolean.parseBoolean(parts[7]) : true;
      boolean cacheV = isRed ? Boolean.parseBoolean(parts[8]) : true;

      return new QuaternaryInstruction(
          new QuaternaryOperator(wtype), in1, in2, in3, in4, out, cacheU, cacheV, str);
    } else if (WeightedUnaryMM.OPCODE.equalsIgnoreCase(opcode) // wumm
        || WeightedUnaryMMR.OPCODE.equalsIgnoreCase(opcode)) {
      boolean isRed = WeightedUnaryMMR.OPCODE.equalsIgnoreCase(opcode);

      // check number of fields (4 inputs, output, type)
      if (isRed) InstructionUtils.checkNumFields(str, 8);
      else InstructionUtils.checkNumFields(str, 6);

      // parse instruction parts (without exec type)
      String[] parts = InstructionUtils.getInstructionParts(str);

      String uopcode = parts[1];
      byte in1 = Byte.parseByte(parts[2]);
      byte in2 = Byte.parseByte(parts[3]);
      byte in3 = Byte.parseByte(parts[4]);
      byte out = Byte.parseByte(parts[5]);
      WUMMType wtype = WUMMType.valueOf(parts[6]);

      // in mappers always through distcache, in reducers through distcache/shuffle
      boolean cacheU = isRed ? Boolean.parseBoolean(parts[7]) : true;
      boolean cacheV = isRed ? Boolean.parseBoolean(parts[8]) : true;

      return new QuaternaryInstruction(
          new QuaternaryOperator(wtype, uopcode),
          in1,
          in2,
          in3,
          (byte) -1,
          out,
          cacheU,
          cacheV,
          str);
    } else if (WeightedDivMM.OPCODE.equalsIgnoreCase(opcode) // wdivmm
        || WeightedDivMMR.OPCODE.equalsIgnoreCase(opcode)) {
      boolean isRed = opcode.startsWith("red");

      // check number of fields (4 inputs, output, type)
      if (isRed) InstructionUtils.checkNumFields(str, 8);
      else InstructionUtils.checkNumFields(str, 6);

      // parse instruction parts (without exec type)
      String[] parts = InstructionUtils.getInstructionParts(str);

      final WDivMMType wtype = WDivMMType.valueOf(parts[6]);

      byte in1 = Byte.parseByte(parts[1]);
      byte in2 = Byte.parseByte(parts[2]);
      byte in3 = Byte.parseByte(parts[3]);
      byte in4 = wtype.hasScalar() ? -1 : Byte.parseByte(parts[4]);
      byte out = Byte.parseByte(parts[5]);

      // in mappers always through distcache, in reducers through distcache/shuffle
      boolean cacheU = isRed ? Boolean.parseBoolean(parts[7]) : true;
      boolean cacheV = isRed ? Boolean.parseBoolean(parts[8]) : true;

      return new QuaternaryInstruction(
          new QuaternaryOperator(wtype), in1, in2, in3, in4, out, cacheU, cacheV, str);
    } else // wsigmoid / wcemm
    {
      boolean isRed = opcode.startsWith("red");
      int addInput4 = (opcode.endsWith("wcemm")) ? 1 : 0;

      // check number of fields (3 or 4 inputs, output, type)
      if (isRed) InstructionUtils.checkNumFields(str, 7 + addInput4);
      else InstructionUtils.checkNumFields(str, 5 + addInput4);

      // parse instruction parts (without exec type)
      String[] parts = InstructionUtils.getInstructionParts(str);

      byte in1 = Byte.parseByte(parts[1]);
      byte in2 = Byte.parseByte(parts[2]);
      byte in3 = Byte.parseByte(parts[3]);
      byte out = Byte.parseByte(parts[4 + addInput4]);

      // in mappers always through distcache, in reducers through distcache/shuffle
      boolean cacheU = isRed ? Boolean.parseBoolean(parts[6 + addInput4]) : true;
      boolean cacheV = isRed ? Boolean.parseBoolean(parts[7 + addInput4]) : true;

      if (opcode.endsWith("wsigmoid"))
        return new QuaternaryInstruction(
            new QuaternaryOperator(WSigmoidType.valueOf(parts[5])),
            in1,
            in2,
            in3,
            (byte) -1,
            out,
            cacheU,
            cacheV,
            str);
      else if (opcode.endsWith("wcemm"))
        return new QuaternaryInstruction(
            new QuaternaryOperator(WCeMMType.valueOf(parts[6])),
            in1,
            in2,
            in3,
            (byte) -1,
            out,
            cacheU,
            cacheV,
            str);
    }

    return null;
  }