244 lines
8.0 KiB
Java
244 lines
8.0 KiB
Java
package de.ph87.electro.circuit.calculation;
|
|
|
|
import de.ph87.electro.CONFIG;
|
|
import de.ph87.electro.circuit.Circuit;
|
|
import de.ph87.electro.circuit.Resistance;
|
|
import de.ph87.electro.circuit.part.Part;
|
|
import de.ph87.electro.circuit.part.node.Node;
|
|
import de.ph87.electro.circuit.part.parts.Capacitor;
|
|
import de.ph87.electro.circuit.part.parts.CurrentSource;
|
|
import de.ph87.electro.circuit.wire.Wire;
|
|
import lombok.Getter;
|
|
import lombok.NonNull;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.apache.commons.math3.linear.*;
|
|
|
|
import java.time.Duration;
|
|
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 {
|
|
|
|
private final List<Node> nodes = 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 Calculation(final int numNodes) {
|
|
matrix = new Array2DRowRealMatrix(numNodes, numNodes);
|
|
currents = new ArrayRealVector(numNodes);
|
|
}
|
|
|
|
private Calculation(@NonNull final Set<Node> connectedNodes, @NonNull final CurrentSource pivot, @NonNull final Duration dt) {
|
|
for (final Node node : connectedNodes) {
|
|
if (node instanceof final Node partNode) {
|
|
parts.add(partNode.getPart());
|
|
}
|
|
if (!nodes.contains(node) && node != pivot.getMinus()) {
|
|
// pivot.minus is GND and cannot be part of the matrix (linear dependency)
|
|
this.nodes.add(node);
|
|
}
|
|
wires.addAll(node.getWires());
|
|
}
|
|
|
|
matrix = new Array2DRowRealMatrix(this.nodes.size(), this.nodes.size());
|
|
currents = new ArrayRealVector(this.nodes.size());
|
|
fromSchematic();
|
|
solve();
|
|
toSchematic(dt);
|
|
}
|
|
|
|
public static List<Calculation> calculate(@NonNull final Circuit circuit, @NonNull final Duration dt) {
|
|
final List<Calculation> calculations = new ArrayList<>();
|
|
final List<CurrentSource> currentSources = new ArrayList<>(circuit.streamParts().flatMap(CurrentSource::filterCast).toList());
|
|
while (!currentSources.isEmpty()) {
|
|
final CurrentSource pivot = currentSources.removeFirst();
|
|
final Set<Node> connectedNodes = new HashSet<>();
|
|
pivot.getPlus().collectConnectedNodes(connectedNodes);
|
|
pivot.getMinus().collectConnectedNodes(connectedNodes);
|
|
connectedNodes.stream().map(Node::getPart).flatMap(CurrentSource::filterCast).forEach(currentSources::remove);
|
|
calculations.add(new Calculation(connectedNodes, pivot, dt));
|
|
}
|
|
return calculations;
|
|
}
|
|
|
|
private void fromSchematic() {
|
|
wires.forEach(
|
|
wire -> addResistor(wire.getA(), wire.getB(), CONFIG.RESISTANCE_MIN)
|
|
);
|
|
parts.forEach(part -> {
|
|
for (final Resistance resistance : part.getResistances()) {
|
|
addResistor(resistance.a, resistance.b, resistance.getResistance());
|
|
}
|
|
if (part instanceof final CurrentSource currentSource) {
|
|
if (!(currentSource instanceof final Capacitor capacitor) || !capacitor.isCharging()) {
|
|
final double current = currentSource.getVoltage() / currentSource.getInnerResistance().getResistance();
|
|
final int indexMinus = getNodeIndex(currentSource.getMinus());
|
|
final int indexPlus = getNodeIndex(currentSource.getPlus());
|
|
addCurrentSource(indexMinus, indexPlus, current);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void toSchematic(@NonNull final Duration dt) {
|
|
wires.forEach(wire -> wire.setCurrent(getCurrent(wire)));
|
|
parts.forEach(part -> {
|
|
for (final Resistance resistance : part.getResistances()) {
|
|
resistance.setCurrent(getCurrent(resistance));
|
|
}
|
|
part.getNodes().forEach(node -> node.setVoltage(getPotential(node)));
|
|
part.postCalculate(dt);
|
|
});
|
|
}
|
|
|
|
private double getPotential(@NonNull final Node node) {
|
|
final int index = getNodeIndex(node);
|
|
if (index < 0) {
|
|
return 0; // per definition
|
|
}
|
|
if (potentials == null) {
|
|
return Double.NaN;
|
|
}
|
|
return potentials.getEntry(index);
|
|
}
|
|
|
|
private double getCurrent(@NonNull final Resistance resistance) {
|
|
final int indexA = getNodeIndex(resistance.getA());
|
|
final int indexB = getNodeIndex(resistance.getB());
|
|
final double conductance = indexA >= 0 && indexB >= 0 ? matrix.getEntry(indexA, indexB) : 1 / CONFIG.RESISTANCE_MIN;
|
|
final double potentialDifference = getPotential(resistance.getB()) - getPotential(resistance.getA());
|
|
return conductance * potentialDifference;
|
|
}
|
|
|
|
private int getNodeIndex(@NonNull final Node node) {
|
|
for (int i = 0; i < nodes.size(); i++) {
|
|
if (nodes.get(i) == node) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public void addResistor(final Node a, final Node b, final double resistance) {
|
|
final int indexA = getNodeIndex(a);
|
|
final int indexB = getNodeIndex(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 addCurrentSource(final int index0, final int index1, final double current) {
|
|
if (index0 >= 0) {
|
|
currents.setEntry(index0, currents.getEntry(index0) - current);
|
|
}
|
|
if (index1 >= 0) {
|
|
currents.setEntry(index1, currents.getEntry(index1) + current);
|
|
}
|
|
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 final String format;
|
|
|
|
private int integer;
|
|
|
|
private int decimal;
|
|
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
} |