KinderElektro/src/main/java/de/ph87/electro/circuit/calculation/Calculation.java
2024-05-22 13:54:45 +02:00

246 lines
8.0 KiB
Java

package de.ph87.electro.circuit.calculation;
import de.ph87.electro.circuit.Circuit;
import de.ph87.electro.circuit.wire.Wire;
import de.ph87.electro.circuit.part.InnerConnection;
import de.ph87.electro.circuit.part.junction.Junction;
import de.ph87.electro.circuit.part.Part;
import de.ph87.electro.circuit.part.parts.PartBattery;
import de.ph87.electro.circuit.part.parts.PartLight;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.math3.linear.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.lang.Math.max;
@Slf4j
@Getter
public class Calculation {
public static final double NO_RESISTANCE = 1e-12;
private static final double FULL_ADMITTANCE = 1 / NO_RESISTANCE;
private final List<Junction> junctions = new ArrayList<>();
private final Set<Part> parts = new HashSet<>();
private final Set<Wire> wires = new HashSet<>();
private final RealMatrix matrix;
private final RealVector currents;
private RealVector potentials = null;
public static List<Calculation> calculate(final Circuit circuit) {
final List<Calculation> calculations = new ArrayList<>();
final List<PartBattery> batteries = new ArrayList<>(circuit.streamParts().flatMap(PartBattery::filterCast).toList());
while (!batteries.isEmpty()) {
final PartBattery pivot = batteries.removeFirst();
final Set<Junction> connectedJunctions = new HashSet<>();
pivot.getPlus().collectConnectedJunctions(connectedJunctions);
pivot.getMinus().collectConnectedJunctions(connectedJunctions);
connectedJunctions.stream().map(Junction::getOwner).flatMap(PartBattery::filterCast).forEach(batteries::remove);
calculations.add(new Calculation(connectedJunctions, pivot));
}
return calculations;
}
public Calculation(final int numNodes) {
matrix = new Array2DRowRealMatrix(numNodes, numNodes);
currents = new ArrayRealVector(numNodes);
}
private Calculation(final Set<Junction> connectedJunctions, final PartBattery pivot) {
for (final Junction junction : connectedJunctions) {
parts.add(junction.getOwner());
if (!junctions.contains(junction) && junction != pivot.getMinus()) {
// pivot.minus is GND and cannot be part of the matrix (linear dependency)
this.junctions.add(junction);
}
wires.addAll(junction.getWires());
}
matrix = new Array2DRowRealMatrix(this.junctions.size(), this.junctions.size());
currents = new ArrayRealVector(this.junctions.size());
fromSchematic();
solve();
toSchematic();
}
private void fromSchematic() {
wires.forEach(
wire -> addResistor(wire.getA(), wire.getB(), NO_RESISTANCE)
);
parts.forEach(part -> {
for (final InnerConnection innerConnection : part.getInnerConnections()) {
addResistor(innerConnection.a, innerConnection.b, innerConnection.resistance);
}
if (part instanceof final PartBattery battery) {
addBattery(battery);
} else if (part instanceof final PartLight light) {
addResistor(light.getA(), light.getB(), light.getResistance());
}
});
}
private void toSchematic() {
wires.forEach(wire -> wire.setCurrent(getCurrent(wire)));
parts.forEach(part -> {
part.getJunctions().forEach(junction -> junction.setVoltage(getPotential(junction)));
part.postCalculate();
});
}
private double getPotential(final @NonNull Junction junction) {
final int index = getJunctionIndex(junction);
if (index < 0) {
return 0; // per definition
}
if (potentials == null) {
return Double.NaN;
}
return potentials.getEntry(index);
}
private double getCurrent(final Wire wire) {
final int indexA = getJunctionIndex(wire.getA());
final int indexB = getJunctionIndex(wire.getB());
final double conductance = indexA >= 0 && indexB >= 0 ? matrix.getEntry(indexA, indexB) : FULL_ADMITTANCE;
final double potentialDifference = getPotential(wire.getB()) - getPotential(wire.getA());
return conductance * potentialDifference;
}
private int getJunctionIndex(@NonNull final Junction junction) {
for (int i = 0; i < junctions.size(); i++) {
if (junctions.get(i) == junction) {
return i;
}
}
return -1;
}
public void addResistor(final Junction a, final Junction b, final double resistance) {
final int indexA = getJunctionIndex(a);
final int indexB = getJunctionIndex(b);
addResistor(indexA, indexB, resistance);
}
public void addResistor(final int index0, final int index1, double resistance) {
double conductance = 1.0 / resistance;
if (index0 >= 0) {
matrix.setEntry(index0, index0, matrix.getEntry(index0, index0) + conductance);
}
if (index1 >= 0) {
matrix.setEntry(index1, index1, matrix.getEntry(index1, index1) + conductance);
}
if (index0 >= 0 && index1 >= 0) {
matrix.setEntry(index0, index1, matrix.getEntry(index0, index1) - conductance);
matrix.setEntry(index1, index0, matrix.getEntry(index1, index0) - conductance);
}
potentials = null;
}
public void addBattery(final PartBattery battery) {
final double current = battery.getVoltage() / battery.getResistance();
final int indexMinus = getJunctionIndex(battery.getMinus());
final int indexPlus = getJunctionIndex(battery.getPlus());
addCurrentSource(indexMinus, indexPlus, current, battery.getResistance());
}
public void addCurrentSource(final int index0, final int index1, final double current, final double resistance) {
if (index0 >= 0) {
currents.setEntry(index0, currents.getEntry(index0) - current);
}
if (index1 >= 0) {
currents.setEntry(index1, currents.getEntry(index1) + current);
}
addResistor(index0, index1, resistance);
potentials = null;
}
public void solve() {
final DecompositionSolver solver = new LUDecomposition(matrix).getSolver();
try {
potentials = solver.solve(currents);
} catch (SingularMatrixException e) {
potentials = null;
log.error("Schaltung fehlerhaft: {}", e.getMessage());
}
}
@Override
public String toString() {
final Format matrixFormat = new Format(matrix);
final Format currentsFormat = new Format(currents);
final Format potentialsFormat = new Format(potentials);
final StringBuilder builder = new StringBuilder();
for (int row = 0; row < matrix.getRowDimension(); row++) {
builder.append("[");
String prefix = "";
for (int c = 0; c < matrix.getColumnDimension(); c++) {
builder.append(prefix);
builder.append(matrixFormat.format(matrix.getEntry(row, c)));
prefix = ", ";
}
final String potentialString = potentialsFormat.format(potentials.getEntry(row));
final String currentString = currentsFormat.format(currents.getEntry(row));
builder.append("] x [%s V] = [%s A]\n".formatted(potentialString, currentString));
}
return builder.toString();
}
@Getter
private static final class Format {
private int integer;
private int decimal;
private final String format;
public Format(final RealMatrix matrix) {
for (int r = 0; r < matrix.getRowDimension(); r++) {
for (int c = 0; c < matrix.getColumnDimension(); c++) {
append(matrix.getEntry(r, c));
}
}
format = calculateFormat();
}
public Format(final RealVector vector) {
for (int r = 0; r < vector.getDimension(); r++) {
append(vector.getEntry(r));
}
format = calculateFormat();
}
private void append(final double value) {
final String string = "%+f".formatted(value);
final String[] parts = string.split("[.,]");
integer = max(integer, parts[0].length());
if (parts.length > 1) {
decimal = max(decimal, parts[1].length());
}
}
private String calculateFormat() {
return "%%+%d.%df".formatted(integer + decimal + (decimal > 0 ? 1 : 0), decimal);
}
public String format(final double value) {
return format.formatted(value);
}
}
}