5
\$\begingroup\$

This is exercise 3.2.23. from the book Computer Science An Interdisciplinary Approach by Sedgewick & Wayne:

Write a program that creates an array of charged particles from values given on standard input (each charged particle is specified by its x-coordinate, y-coordinate, and charge value) and produces a visualization of the electric potential in the unit square. To do so, sample points in the unit square. For each sampled point, compute the electric potential at that point (by summing the electric potentials due to each charged particle) and plot the corresponding point in a shade of gray proportional to the electric potential.

The following is the data-type implementation for charged particles from the book which I modified (beautified and added more appropriate names):

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 + ")";
    }
}

Here is my program (but to increase the variety I created random particles instead of reading from input data):

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 is a simple API written by the authors of the book. I checked my program and it works. Here are two instances of it:

Input: 3840 2160 200 0.5 10

Output:

enter image description here

Input: 3840 2160 5000 0.5 5

Output:

enter image description here

Is there any way that I can improve my program?

Thanks for your attention.

\$\endgroup\$
10
\$\begingroup\$
  • Test cases are not convincing. I understand that the program doesn't crash, and by visual inspection of the code it seems to do the right things. However, it is pretty much impossible to check how the potential of 5000 random charges should look like. I'd rather see at least two elementary potentials: one of a single charge, and one of a dipole.

  • No naked loops please. Every loop implements an important part of the job, and deserves a name. Consider

      public static void main(String[] args) {
          ....
          Charge[] charges = generateRandomCharges(numberOfCharges, chargeSignDistribution, chargeSizeDistribution);
          double[][] potentials = computePotentials(width, height, charges);
          rescalePotentials(width, height, potentials);
          ....
    
  • Rescaling is a misnomer. What you do is called clamping.

    It also seems that shifting values by 180 belongs to the clamping phase. Shifting and clamping together prepare for visualization, and have nothing to do with computing potentials.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You are absolutely right. Actually for myself to become sure I first checked a dipole. But since the exercise involved displaying many charges, I chose those test cases in here. And I guess adding them now is against the site's rules, right? \$\endgroup\$ – Khashayar Baghizadeh Sep 18 at 21:01
8
\$\begingroup\$

Some minor improvements can be applied to your code; you have the following method:

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);
}

You could apply the Math.hypot method to calculate the distance between two points in this way :

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;
}

Note: as ex nihilo observed in his comment, Math.hypot method should be used because it returns sqrt(x2 +y2) without intermediate overflow or underflow, cases not handled by the method present in your code.

You have the following toString method in your code:

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

To improve readibility you could apply the String.format method in this way:

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

In your code the following lines are present:

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

You could rewrite them in this way:

double charge = Math.random() * chargeSizeDistribution;
if (Math.random() < chargeSignDistribution) {
   charge -= chargeSizeDistribution;
} 
| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ You should explain why Math.hypot not only could, but should be used. In C it is critical to know about hypot for numeric code. A naive calculation like h = sqrt(x*x + y*y) can overflow or underflow in the multiplications, and this is a real possibility in modelling code as in OP's question. In C overflow/underflow can lead to undefined behavior; hypot guarantees that this will not happen. Java makes a similar guarantee for intermediate results with Math.hypot. \$\endgroup\$ – ex nihilo Sep 19 at 16:04
  • \$\begingroup\$ @exnihilo You are right, I had been too schematic in my answer and I'm adding a note to my answer referring to your comment. \$\endgroup\$ – dariosicily Sep 20 at 7:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.