電位の可視化

Sep 18 2020

これは演習3.2.23です。著書ComputerScienceからSedgewick&Wayneによる学際的アプローチ:

標準入力で与えられた値から荷電粒子の配列を作成し(各荷電粒子はそのx座標、y座標、および電荷値によって指定されます)、単位正方形の電位を視覚化するプログラムを作成します。これを行うには、単位正方形のポイントをサンプリングします。サンプリングされた各ポイントについて、そのポイントでの電位を計算し(各荷電粒子による電位を合計することにより)、電位に比例する灰色の陰影で対応するポイントをプロットします。

以下は、私が変更した(美化してより適切な名前を追加した)本の荷電粒子のデータ型の実装です。

public class Charge {
    private final double pointXCoordinate;
    private final double pointYCoordinate;
    private final double charge;
    public Charge(double pointXCoordinate, double pointYCoordinate, double charge) {
        this.pointXCoordinate = pointXCoordinate;
        this.pointYCoordinate = pointYCoordinate;
        this.charge = charge;
    }
    public double calculatePotentialAt(double otherPointXCoordinate, double otherPointYCoordinate) {
        double electrostaticConstant = 8.99e09;
        double distanceInXCoordinate = otherPointXCoordinate - pointXCoordinate;
        double distanceInYCoordinate = otherPointYCoordinate - pointYCoordinate;
        return electrostaticConstant * charge / Math.sqrt(distanceInXCoordinate * distanceInXCoordinate + distanceInYCoordinate * distanceInYCoordinate);
    }
    public String toString() {
        return charge + " at (" + pointXCoordinate + "," + pointYCoordinate + ")";
    }
}

これが私のプログラムです(ただし、入力データから読み取る代わりにランダムパーティクルを作成した多様性を増やすため):

import java.awt.Color;
public class Potential {
    public static void main(String[] args) {
        int width = Integer.parseInt(args[0]);
        int height = Integer.parseInt(args[1]);
        int numberOfCharges = Integer.parseInt(args[2]);
        double chargeSignDistribution = Double.parseDouble(args[3]);
        double chargeSizeDistribution = Double.parseDouble(args[4]);
        Charge[] charges = new Charge[numberOfCharges];
        for (int i = 0; i < numberOfCharges; i++) {
            double pointXCoordinate = Math.random();
            double pointYCoordinate = Math.random();
            double charge = 0;
            if (Math.random() < chargeSignDistribution) charge = -chargeSizeDistribution + Math.random() * chargeSizeDistribution;
            else charge = Math.random() * chargeSizeDistribution;
            charges[i] = new Charge(pointXCoordinate, pointYCoordinate, charge);
        }
        double[][] potentials = new double[width][height];
        for (int j = 0; j < width; j++) {
            for (int i = 0; i < height; i++) {
                for (int k = 0; k < numberOfCharges; k++) {
                    potentials[j][i] += charges[k].calculatePotentialAt(1.0 * j / width, 1.0 * i / height);
                }
                /*
                Obtained '180' by experimentation.
                Scaled down by the amount of electrostatic constant (9e09).
                */
                potentials[j][i] = 180 + potentials[j][i] / 9e09;
            }
        }
        int[][] rescaledPotentials = new int[width][height];
        for (int j = 0; j < width; j++) {
            for (int i = 0; i < height; i++) {
                if (potentials[j][i] < 0) rescaledPotentials[j][i] = 0;
                else if (potentials[j][i] > 255) rescaledPotentials[j][i] = 255;
                else rescaledPotentials[j][i] = (int) potentials[j][i];
            }
        }
        Color[][] colors = new Color[width][height];
        for (int j = 0; j < width; j++) {
            for (int i = 0; i < height; i++) {
                int c = rescaledPotentials[j][i];
                colors[j][i] = new Color(c, c, c);
            }
        }
        Picture picture = new Picture(width, height);
        for (int j = 0; j < width; j++) {
            for (int i = 0; i < height; i++) {
                picture.set(j, i, colors[j][i]);
            }
        }
        picture.show();
    }
}

Pictureは、本の著者によって書かれた単純なAPIです。プログラムをチェックしましたが、動作します。これがその2つの例です:

入力:3840 2160 200 0.5 10

出力:

入力:3840 2160 5000 0.5 5

出力:

プログラムを改善する方法はありますか?

ご清聴ありがとうございました。

回答

10 vnp Sep 19 2020 at 03:14
  • テストケースは説得力がありません。プログラムがクラッシュしないことを理解しています。コードを視覚的に調べると、正しいことをしているように見えます。ただし、5000回のランダムチャージの可能性がどのように見えるかを確認することはほとんど不可能です。むしろ、少なくとも2つの基本ポテンシャルを確認したいと思います。1つは単一電荷で、もう1つは双極子です。

  • 裸のループはご遠慮ください。すべてのループはジョブの重要な部分を実装し、名前に値します。検討する

      public static void main(String[] args) {
          ....
          Charge[] charges = generateRandomCharges(numberOfCharges, chargeSignDistribution, chargeSizeDistribution);
          double[][] potentials = computePotentials(width, height, charges);
          rescalePotentials(width, height, potentials);
          ....
    
  • 再スケーリングは誤った呼び方です。あなたがすることはクランプと呼ばれます。

    また、値を180だけシフトすることは、クランプフェーズに属するようです。シフトとクランプを一緒に行うことで視覚化の準備が整い、コンピューティングの可能性とは何の関係もありません。

8 dariosicily Sep 19 2020 at 00:34

いくつかのマイナーな改善をコードに適用できます。次の方法があります。

public double calculatePotentialAt(double otherPointXCoordinate, double otherPointYCoordinate) {
    double electrostaticConstant = 8.99e09;
    double distanceInXCoordinate = otherPointXCoordinate - pointXCoordinate;
    double distanceInYCoordinate = otherPointYCoordinate - pointYCoordinate;
    return electrostaticConstant * charge / Math.sqrt(distanceInXCoordinate * distanceInXCoordinate + distanceInYCoordinate * distanceInYCoordinate);
}

このMath.hypot方法を適用して、次の方法で2点間の距離を計算できます。

public double calculatePotentialAt(double otherPointXCoordinate, double otherPointYCoordinate) {
    double electrostaticConstant = 8.99e09;
    double distanceInXCoordinate = otherPointXCoordinate - pointXCoordinate;
    double distanceInYCoordinate = otherPointYCoordinate - pointYCoordinate;
    double distance = Math.hypot(distanceInXCoordinate, distanceInYCoordinate);
    return electrostaticConstant * charge / distance;
}

注:ex nihiloが彼のコメントで観察したように、Math.hypotメソッドは中間のオーバーフローまたはアンダーフローなしでsqrt(x2 + y2)を返すため、メソッドを使用する必要があります。コードに存在するメソッドで処理されない場合です。

toStringコードには次のメソッドがあります。

public String toString() {
    return charge + " at (" + pointXCoordinate + "," + pointYCoordinate + ")";
}

読みやすさを向上させるために、次のString.format方法でメソッドを適用できます。

public String toString() {
    return String.format("%f at (%f,%f)", charge, pointXCoordinate, pointYCoordinate);
}

コードには次の行があります。

double charge = 0;
if (Math.random() < chargeSignDistribution) charge = -chargeSizeDistribution + Math.random() * chargeSizeDistribution;
else charge = Math.random() * chargeSizeDistribution;

次のように書き直すことができます。

double charge = Math.random() * chargeSizeDistribution;
if (Math.random() < chargeSignDistribution) {
   charge -= chargeSizeDistribution;
}