Visualização do potencial elétrico
Este é o exercício 3.2.23. do livro Computer Science An Interdisciplinary Approach, de Sedgewick & Wayne:
Escreva um programa que crie uma matriz de partículas carregadas a partir de valores dados na entrada padrão (cada partícula carregada é especificada por sua coordenada x, coordenada y e valor de carga) e produza uma visualização do potencial elétrico no quadrado da unidade. Para fazer isso, experimente pontos no quadrado da unidade. Para cada ponto amostrado, calcule o potencial elétrico naquele ponto (somando os potenciais elétricos devido a cada partícula carregada) e plote o ponto correspondente em um tom de cinza proporcional ao potencial elétrico.
A seguir está a implementação do tipo de dados para partículas carregadas do livro que modifiquei (embelezei e adicionei nomes mais apropriados):
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 + ")";
}
}
Aqui está meu programa (mas para aumentar a variedade, criei partículas aleatórias em vez de ler os dados de entrada):
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();
}
}
Imagem é uma API simples escrita pelos autores do livro. Verifiquei meu programa e funciona. Aqui estão duas instâncias dele:
Entrada: 3840 2160 200 0,5 10
Resultado:
Entrada: 3840 2160 5000 0,5 5
Resultado:
Existe alguma maneira de melhorar meu programa?
Agradecimentos para sua atenção.
Respostas
Os casos de teste não são convincentes. Eu entendo que o programa não trava e, pela inspeção visual do código, parece fazer as coisas certas. No entanto, é praticamente impossível verificar como deve ser o potencial de 5.000 cargas aleatórias. Prefiro ver pelo menos dois potenciais elementares: um de uma única carga e um de um dipolo .
Sem loops nus, por favor. Cada loop implementa uma parte importante do trabalho e merece um nome. Considerar
public static void main(String[] args) { .... Charge[] charges = generateRandomCharges(numberOfCharges, chargeSignDistribution, chargeSizeDistribution); double[][] potentials = computePotentials(width, height, charges); rescalePotentials(width, height, potentials); ....Reescalonar é um nome impróprio. O que você faz é chamado de aperto.
Também parece que o deslocamento dos valores em 180 pertence à fase de fixação . Deslocar e prender juntos prepara a visualização e não tem nada a ver com potenciais de computação.
Algumas pequenas melhorias podem ser aplicadas ao seu código; você tem o seguinte método:
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);
}
Você pode aplicar o Math.hypotmétodo para calcular a distância entre dois pontos desta forma:
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;
}
Nota: como ex nihilo observou em seu comentário, o Math.hypotmétodo deve ser usado porque retorna sqrt (x2 + y2) sem estouro ou estouro intermediário , casos não tratados pelo método presente em seu código.
Você tem o seguinte toStringmétodo em seu código:
public String toString() {
return charge + " at (" + pointXCoordinate + "," + pointYCoordinate + ")";
}
Para melhorar a legibilidade, você pode aplicar o String.formatmétodo desta forma:
public String toString() {
return String.format("%f at (%f,%f)", charge, pointXCoordinate, pointYCoordinate);
}
Em seu código, as seguintes linhas estão presentes:
double charge = 0;
if (Math.random() < chargeSignDistribution) charge = -chargeSizeDistribution + Math.random() * chargeSizeDistribution;
else charge = Math.random() * chargeSizeDistribution;
Você pode reescrevê-los desta forma:
double charge = Math.random() * chargeSizeDistribution;
if (Math.random() < chargeSignDistribution) {
charge -= chargeSizeDistribution;
}