MathematicaのNMinimize, NMinValueのバグ


Ver.11で修正されました。

Mathematica 9.0, 10.0, 10.1, 10.2, 10.3, 10.4.1 for Microsoft Windows (64-bit)と10.0.0 for Linux ARM (32-bit)でのことです。

a2 + c2 == 1, b2 + d2 == 1という条件の下で、次のような関数の最小値を求めたいとします。

Mathematicaには最大・最小を求めるためのさまざまな関数が用意されていますが、NMinimizeMinimizeNMinValueMinValueは、頭にNをつけるかどうかで、数値的な方法と解析的な方法を切り替えられて便利です。最小値とその時の各変数の値を知りたいときは[N]Minimize、最小値だけを知りたいときは[N]MinValueを使います。

しかし、ちょっとうまくいかない例に遭遇しました。バグが修正されたバージョンを受け取るために、プレミアユーザになっています。

問題その1

f1 = Sqrt[
 1 + 4 Sqrt[3] b c d + d^2 - 5 c^2 (-1 + d^2) + 
  2 a (-Sqrt[2] b d + Sqrt[6] c (-1 + d^2))];

NMinimize[{f1, a^2 + c^2 == 1 && b^2 + d^2 == 1}, {a, b, c, d}]

このように実行すると、

関数の値4.35271 +1.45672\ Iは{a,b,c,d} = {0.79784,1.52261,-0.444508,0.634251}において実数ではありません.

という警告とともに結果{1.22618, {a -> -0.82781, b -> 0.492467, c -> 0.224354, d -> -0.333602}}が返ります。このとき、a2 + c2は約0.74、b2 + d2は約0.35なので、与えた制約条件から大きくずれています。

問題その2

f2の最小値を求めようとしたときにも問題が起こります。

f2 = Sqrt[
  1 - 4 Sqrt[3] b c d + d^2 - 5 c^2 (-1 + d^2) + 
   2 a (-Sqrt[2] b d - Sqrt[6] c (-1 + d^2))];

NMinimize[{f2, a^2 + c^2 == 1 && b^2 + d^2 == 1}, {a, b, c, d}]

このように実行しても、入力がそのまま返ってくるだけで、まとも(?)な結果が得られません(間違った答えが返ってくるよりはましかもしれません)。

この問題は、WolframAlphaでも起こります(2015年5月17日確認)。

f1に関しては間違った結果が返ってきます(下はNMinValueの場合。NMinimizeでも同様)。

f2に関しては、珍しいことに、WolframAlphaは完全に沈黙します(下はNMinValueの場合。NMinimizeは違う結果になります)。

MathematicaからWolframAlphaに問い合わせたときはまた違う結果で、f2は解釈できないとのことでした。ほとんど同じf1は解釈できたので、そんなことはないと思うのですが。

そもそも、こういう関数の最小値を何の工夫もせずに得ようとするのが間違いなのかもしれませんが、あえて工夫をしないのにも理由があるのです。それについては別の機会に書きます。

MathematicaのFindClustersのバグ(Ver. 11で解決)


Ver.11で修正されました。

Mathematica 8.0, 9.0, 10.0, 10.1, 10.2, 10.3, 10.4.1 for Microsoft Windows (64-bit)と10.0.0, 10.3.1 for Linux ARM (32-bit)でのことです。

Mathematicaでクラスター分析をするFindClustersは、このデータを処理できません。(RSSリーダーでは表示されないかもしれません。)

このバグは報告済みですが、解決方法はわかっていません。バグが修正されたバージョンを受け取るために、プレミアユーザになっています。

しょうがないからRでやろう、せっかくだからMathematica 9で導入されたRLinkにしようと思ったのですが(そういう欲求があることは、開発元も認めているわけで:階層的クラスタリング)、コサイン類似度を計算するためのcosineを、64bit版Mathematicaから使う方法がよくりませんでした。

Mathematicaでは、FindClustersの他にAgglomerateClusteringComponentsでも階層的クラスタリングができて、整理されていないのもちょっと困ります。(Agglomerateにもバグが、ClusteringComponentsにも困ったところがあります。)

というわけで、Rで実行する方法は以下の通りです。gistのようにSSL (https)で取得するファイルには「ssl.verifypeer = F」でアクセスします。

(解決)MathematicaのTableのバグ


Mathematica 10.1 for Windows (64-bit)で修正されました。

Mathematica 8.0.4, 9.0.1, 10.0.2 for Windows (64-bit)と10.0.0 for Linux ARM (32-bit)でのことです。

Mathematicaで確率分布から疑似乱数を作るRandomVariateを試していたら、こんなバグを見つけました。(RSSリーダーでは表示されないかもしれません。)

開発元に報告したら、「長さが250以上のリストを作るときにはコンパイルが自動的に試みられるが、そこに問題がある。以下のコマンドでそれを無効にせよ」とのことでした。

SetSystemOptions["CompileOptions" -> "TableCompileLength" -> Infinity]

バグが修正されたバージョンを受け取るために、プレミアユーザになっています。

(解決)Javaでサニタイズするときの注意—Apache Commons Lang 2.xと3.0の場合


Commons Lang 3.0.1で修正されています。

Java 5をサポートする「Apache Commons Lang 3.0」がリリース、というニュースがありました。正確には、Java 1.4以下をサポートしない「Apache Commons Lang 3.0」がリリース、だと思いますが、それはまあいいとして、これに関連する少し深刻な話を忘れないうちに書いておきましょう。

「Javaプログラマは文字列の操作方法を確認した方がいいかもしれない」のつづきです。

HTML文書を書く時には、「𠮷野家(>_<)」のような文字列は、「𠮷野家(&gt;_&lt;)」のように書かなければなりません。「<」や「>」、「&」などは特殊な文字なので、文書中にそのまま書くことはできないのです。

効率を気にしないなら、次のように書いて置換するのが簡単です。

str = "\uD842\uDFB7\u91CE\u5BB6(>_<)";
str = str.replace("&", "&amp;")
        .replace("<", "&lt;").replace(">", "&gt;");
System.out.println(str);
//𠮷野家(&gt;_&lt;)

しかし、このようなコードをいつも書くのは面倒ですし、順番を間違える危険もあります。実は「'」や「"」を置換したかったりもします。

そこで、HTML中に直接書けない文字を置換するためのライブラリを使います。Apache Commons Langはそのようなライブラリの一つで、拙著『Webアプリケーション構築入門』でも紹介しました。次のように使います。

まずは最近リリースされたCommons Lang 3.0の場合です。

String str = "文字列(>_<)";
str = org.apache.commons.lang3.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//文字列(&gt;_&lt;)

3.0は出たばかりですし、Java 5以上が必須なので、まだ2.xの方がほとんどでしょう。2.xでは次のようになります。

String str = "文字列(>_<)";
str = org.apache.commons.lang.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//&#25991;&#23383;&#21015;(&gt;_&lt;)

Commons Lang 2.xでは、ASCII以外の文字は「&#コードポイント(10進表記);」という形(数値文字参照)に置換されるので、人間にはわかりにくくなっていますが、ウェブブラウザでなら「文字列(>_<)」と表示されます。

2.xと3.0の間には、(1) パッケージ名(2.6はlang、3.0はlang3)(2) 数値文字参照の使用(2.6では使う、3.0では使わない)、に関して違いがあります。

ここまでは問題ありませんが、対象文字列がサロゲートペアを含むようになると、いやな感じになります。

まずは3.0の場合。

String str = "\uD842\uDFB7\u91CE\u5BB6(>_<)";
str = org.apache.commons.lang3.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//𠮷?野家(&gt;_&lt;

変なところに「?」が入って、最後の「)」が無くなっていますが、これは間違いです。数値文字参照を使わない3.0では、「𠮷野家(&gt;_&lt;)」にならなければなりません。

バグレポートを送ったので、この問題は3.0.1で解決されるはずです。(追記:修正されました。)

サロゲートペアを使う可能性が少しでもある人は、Commons Lang 3.0はスルーしましょう。

パッチを見てもらえばわかりますが、「1文字ずつ処理する」部分にバグがあったのです(参考:Javaプログラマは文字列の操作方法を確認した方がいいかもしれない)。

とりあえずの結論:Javaの文字列処理は難しすぎる。

次にCommons Lang 2.xの場合です。

str = "\uD842\uDFB7\u91CE\u5BB6(>_<)";
str = org.apache.commons.lang.StringEscapeUtils.escapeXml(str);
System.out.println(str);
//&#55362;&#57271;&#37326;&#23478;(&gt;_&lt;)

「𠮷」が「&#55362;&#57271;」という2つの数値文字参照になっていますが、これは間違いです。「&#134071;」という1つの数値文字参照にならなければなりません(参考:Using character escapes in markup and CSS)。

この問題を解決するパッチを送りましたが、「3.0で修正されている」としてスルーされてしまいました。間違いが2.xで修正されるとしたら、動作をそろえるためにStringEscapeUtils.unescapeXml(str)にも修正が必要だと思い、そのためのパッチも送ったのですが、こちらは現時点では間違った動作はしていないので、やはりスルーされてしまいました。

「3.0で修正されている」とは言っても、先に述べたように、2.xと3.0ではサポートするJavaのバージョンが違いますし、パッケージ名も変わっているので、2.xでも直しておいた方がいいと思ったのですが、まあ、仕方ありません。

Commons Lang 2.xを使っている人は、「サロゲートペアはサポートしない」と仕様書に明記しておきましょう。

最終結論:Javaの文字列処理は難しすぎる。

(解決)64ビット版Java仮想マシンのおかしな振る舞い(整数編)


1.7.0ではこの現象は発生しませんでした。よかった。

64ビット版Java仮想マシンは、整数がオーバーフローする(可能性がある?)場合に、おかしな振る舞いをするようです。

例1

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6980767で報告されている例です。

public class Bug1 {

  public static void main(String[] args) {
    System.out.println(System.getProperty("java.runtime.version"));
    int foobar = Integer.MAX_VALUE;
    for (int value = 0; value <= foobar; value++) {
      if (value % 100000000 == 0) {
        System.out.println("At " + value);
      }
    }
    System.out.println("DONE");
  }
}

私の環境では、次のような実行結果になりましたが、これは適切なものではありません。

1.6.0_22-b04
At 0
DONE

ちなみに、32ビット版Java仮想マシンなら適切な結果になります。(略)

例2

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6979077で報告されている例です。

public class Bug2 {

  public static void main(String[] args) {
    System.out.println(System.getProperty("java.runtime.version"));
    for (int i = 0; i < 20; i++) {
      work(2147463647, Integer.MAX_VALUE);
    }
  }

  static void work(int l, int u) {
    int runs = 0;
    for (int i = l; (0 < i) && (i <= u); i++) {
      runs++;
    }
    System.out.println(runs);
  }
}

私の環境では、次のような実行結果になりましたが、これは適切なものではありません。

1.6.0_22-b04
20001
20001
20001
20001
20000
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025
1025

ちなみに、32ビット版Java仮想マシンなら適切な結果になります。(略)

例3

フェルマーの最終定理の「反例」(C言語編)で紹介した例は、Javaでも有効なはず、つまりフェルマーの最終定理の反例を間違って表示して終了するはずですが、以下のコードは65ビット版Java仮想マシンでは止まりません(数学的には正しいけれど)。

public class Bug3 {

  public static void main(String[] args) {
    System.out.println(System.getProperty("java.runtime.version"));
    for (int c = 1; true; c++) {
      for (int a = 1; a < c; a++) {
        for (int b = a; b < c; b++) {
          if (a * a * a + b * b * b == c * c * c) {
            System.out.println("a = " + a + ", b = " + b + ", c = " + c);
            System.exit(0);
          }
        }
      }
    }
  }
}

「true」を「c < Integer.MAX_VALUE」に変えると適切な結果になります。

ちなみに、32ビット版Java仮想マシンなら適切な結果になります。(略)

どういうことなのかわかる人がいたら教えてほしいです。原因がわかるまでは、64ビット版Java仮想マシンは、ちょっとこわくて使えません。

1.6.0_26-b03でも確認しました。32ビット版は大丈夫で64ビット版はおかしいです。