Compare commits
No commits in common. "4b2b9407e1c1de6c9e0cb1e44051de5402a8d1e8" and "04fe1803347f26f2a5f99b5619c229f595a63cbb" have entirely different histories.
4b2b9407e1
...
04fe180334
@ -77,7 +77,7 @@ public class CONFIG {
|
||||
|
||||
public static final BasicStroke SWITCH_STROKE = new BasicStroke(15);
|
||||
|
||||
public static boolean SHOW_WIRE_DETAILS = true;
|
||||
public static boolean SHOW_WIRE_DETAILS = false;
|
||||
|
||||
public static boolean SHOW_NODE_VOLTAGES = false;
|
||||
|
||||
|
||||
@ -2,13 +2,13 @@ package de.ph87.electro;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.CircuitPanel;
|
||||
import de.ph87.electro.circuit.demo.Demos;
|
||||
import de.ph87.electro.sidebar.Sidebar;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
import static de.ph87.electro.CONFIG.RASTER;
|
||||
import static de.ph87.electro.circuit.demo.Demos.capacitor;
|
||||
|
||||
public class Window extends JFrame {
|
||||
|
||||
@ -20,7 +20,7 @@ public class Window extends JFrame {
|
||||
setPreferredSize(new Dimension(1200, 900));
|
||||
setExtendedState(MAXIMIZED_BOTH);
|
||||
|
||||
final Sidebar sidebar = new Sidebar(() -> setCircuit(capacitor()));
|
||||
final Sidebar sidebar = new Sidebar(() -> setCircuit(new Circuit()));
|
||||
final JSplitPane splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sidebar, circuitPanel);
|
||||
|
||||
sidebar.setPreferredSize(new Dimension(calcWidth(3), 0));
|
||||
@ -38,7 +38,7 @@ public class Window extends JFrame {
|
||||
|
||||
public static void main(String[] args) {
|
||||
final Window window = new Window();
|
||||
window.setCircuit(capacitor());
|
||||
window.setCircuit(Demos.potiAndVoltmeter());
|
||||
}
|
||||
|
||||
public void setCircuit(final Circuit circuit) {
|
||||
|
||||
@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
@ -157,17 +156,8 @@ public class Circuit {
|
||||
return new File(file.getAbsolutePath().replaceAll("\\.json$", ".png"));
|
||||
}
|
||||
|
||||
private long dtLast = 0;
|
||||
|
||||
public void evaluate() {
|
||||
final long now = System.nanoTime();
|
||||
if (dtLast == 0) {
|
||||
dtLast = now;
|
||||
return;
|
||||
}
|
||||
final Duration dt = Duration.ofNanos(now - dtLast).dividedBy(10); // TODO remove /10
|
||||
dtLast = now;
|
||||
Calculation.calculate(this, dt);
|
||||
Calculation.calculate(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -28,9 +28,6 @@ public class CircuitPanel extends JPanel {
|
||||
}
|
||||
|
||||
});
|
||||
final Thread thread = new Thread(this::run);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void setCircuit(@NonNull final Circuit circuit) {
|
||||
@ -47,21 +44,4 @@ public class CircuitPanel extends JPanel {
|
||||
mouseAdapter.drawDrag(g);
|
||||
}
|
||||
|
||||
private void run() {
|
||||
final Object lock = new Object();
|
||||
while (true) {
|
||||
circuit.evaluate();
|
||||
repaint();
|
||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait(50);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.electro.circuit;
|
||||
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.parts.*;
|
||||
import de.ph87.electro.common.AbstractDropTarget;
|
||||
|
||||
@ -22,21 +21,21 @@ public class CircuitPanelDropTarget extends AbstractDropTarget {
|
||||
protected boolean drop(final Point point, final String data) {
|
||||
final Point aligned = ALIGN(point);
|
||||
if (data.equals(Battery.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new Battery(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new Battery(aligned));
|
||||
} else if (data.equals(Light.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new Light(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new Light(aligned));
|
||||
} else if (data.equals(Switch1x1.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new Switch1x1(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new Switch1x1(aligned));
|
||||
} else if (data.equals(Switch1x2.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new Switch1x2(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new Switch1x2(aligned));
|
||||
} else if (data.equals(SwitchCross.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new SwitchCross(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new SwitchCross(aligned));
|
||||
} else if (data.equals(Poti.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new Poti(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new Poti(aligned));
|
||||
} else if (data.equals(Voltmeter.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new Voltmeter(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new Voltmeter(aligned));
|
||||
} else if (data.equals(Amperemeter.class.getSimpleName())) {
|
||||
circuitPanel.getCircuit().addPart(new Amperemeter(aligned, Orientation.R0));
|
||||
circuitPanel.getCircuit().addPart(new Amperemeter(aligned));
|
||||
} else {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.electro.circuit;
|
||||
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import de.ph87.electro.circuit.part.parts.Connector;
|
||||
@ -192,7 +191,7 @@ class CircuitPanelMouseAdapter extends MouseAdapter {
|
||||
}
|
||||
|
||||
private void wireBendToNewConnector() {
|
||||
final Connector newConnector = circuitPanel.getCircuit().addPart(new Connector(draggingSub, Orientation.R0));
|
||||
final Connector newConnector = circuitPanel.getCircuit().addPart(new Connector(draggingSub));
|
||||
connectIntermediate(newConnector.getNode());
|
||||
}
|
||||
|
||||
|
||||
@ -5,15 +5,13 @@ 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.part.parts.Battery;
|
||||
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;
|
||||
@ -42,7 +40,7 @@ public class Calculation {
|
||||
currents = new ArrayRealVector(numNodes);
|
||||
}
|
||||
|
||||
private Calculation(@NonNull final Set<Node> connectedNodes, @NonNull final CurrentSource pivot, @NonNull final Duration dt) {
|
||||
private Calculation(final Set<Node> connectedNodes, final Battery pivot) {
|
||||
for (final Node node : connectedNodes) {
|
||||
if (node instanceof final Node partNode) {
|
||||
parts.add(partNode.getPart());
|
||||
@ -58,19 +56,19 @@ public class Calculation {
|
||||
currents = new ArrayRealVector(this.nodes.size());
|
||||
fromSchematic();
|
||||
solve();
|
||||
toSchematic(dt);
|
||||
toSchematic();
|
||||
}
|
||||
|
||||
public static List<Calculation> calculate(@NonNull final Circuit circuit, @NonNull final Duration dt) {
|
||||
public static List<Calculation> calculate(final Circuit circuit) {
|
||||
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 List<Battery> batteries = new ArrayList<>(circuit.streamParts().flatMap(Battery::filterCast).toList());
|
||||
while (!batteries.isEmpty()) {
|
||||
final Battery pivot = batteries.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));
|
||||
connectedNodes.stream().map(Node::getPart).flatMap(Battery::filterCast).forEach(batteries::remove);
|
||||
calculations.add(new Calculation(connectedNodes, pivot));
|
||||
}
|
||||
return calculations;
|
||||
}
|
||||
@ -83,25 +81,23 @@ public class Calculation {
|
||||
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);
|
||||
}
|
||||
if (part instanceof final Battery battery) {
|
||||
final double current = battery.getVoltage() / battery.getInnerResistance().getResistance();
|
||||
final int indexMinus = getNodeIndex(battery.getMinus());
|
||||
final int indexPlus = getNodeIndex(battery.getPlus());
|
||||
addCurrentSource(indexMinus, indexPlus, current);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toSchematic(@NonNull final Duration dt) {
|
||||
private void toSchematic() {
|
||||
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);
|
||||
part.postCalculate();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
package de.ph87.electro.circuit.demo;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.parts.*;
|
||||
import lombok.NonNull;
|
||||
import de.ph87.electro.circuit.part.parts.Battery;
|
||||
import de.ph87.electro.circuit.part.parts.Poti;
|
||||
import de.ph87.electro.circuit.part.parts.Voltmeter;
|
||||
|
||||
import static de.ph87.electro.CONFIG.RASTER;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Demos {
|
||||
|
||||
@NonNull
|
||||
public static Circuit poti() {
|
||||
public static Circuit potiAndVoltmeter() {
|
||||
final Circuit circuit = new Circuit();
|
||||
final Battery battery = circuit.addPart(new Battery(RASTER(1, 0), Orientation.R270));
|
||||
final Poti poti = circuit.addPart(new Poti(RASTER(1, 2), Orientation.R0));
|
||||
final Voltmeter voltmeter = circuit.addPart(new Voltmeter(RASTER(1, 4), Orientation.R90));
|
||||
final Battery battery = circuit.addPart(new Battery(RASTER(1, 0)));
|
||||
final Poti poti = circuit.addPart(new Poti(RASTER(1, 2)));
|
||||
final Voltmeter voltmeter = circuit.addPart(new Voltmeter(RASTER(1, 4)));
|
||||
circuit.connect(battery.getMinus(), poti.getCommon());
|
||||
circuit.connect(battery.getPlus(), poti.getEnd());
|
||||
circuit.connect(poti.getCommon(), voltmeter.a);
|
||||
@ -23,21 +22,4 @@ public class Demos {
|
||||
return circuit;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Circuit capacitor() {
|
||||
final Circuit circuit = new Circuit();
|
||||
|
||||
final Capacitor capacitor = circuit.addPart(new Capacitor(RASTER(1, 0), Orientation.R0));
|
||||
final Voltmeter voltmeter = circuit.addPart(new Voltmeter(RASTER(1, 2), Orientation.R0));
|
||||
final Light light = circuit.addPart(new Light(RASTER(1, 4), Orientation.R0));
|
||||
|
||||
circuit.connect(capacitor.getMinus(), voltmeter.getA());
|
||||
circuit.connect(voltmeter.getA(), light.getA());
|
||||
|
||||
circuit.connect(capacitor.getPlus(), voltmeter.getB());
|
||||
circuit.connect(voltmeter.getB(), light.getB());
|
||||
|
||||
return circuit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import lombok.ToString;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@ -32,7 +31,7 @@ public abstract class Part {
|
||||
private final List<Node> nodes = new ArrayList<>();
|
||||
|
||||
@NonNull
|
||||
protected Orientation orientation;
|
||||
protected Orientation orientation = Orientation.R0;
|
||||
|
||||
@NonNull
|
||||
@Setter
|
||||
@ -45,10 +44,9 @@ public abstract class Part {
|
||||
@NonNull
|
||||
private AffineTransform transform = new AffineTransform();
|
||||
|
||||
protected Part(@NonNull final String name, @NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
protected Part(@NonNull final String name, @NonNull final Point position) {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.name = name;
|
||||
this.orientation = orientation;
|
||||
this.position = position;
|
||||
updateTransform();
|
||||
}
|
||||
@ -142,7 +140,7 @@ public abstract class Part {
|
||||
// -
|
||||
}
|
||||
|
||||
public void postCalculate(final @NonNull Duration dt) {
|
||||
public void postCalculate() {
|
||||
// -
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
@ -9,8 +8,8 @@ import java.awt.*;
|
||||
@Getter
|
||||
public class Amperemeter extends Meter {
|
||||
|
||||
public Amperemeter(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super(MeterType.AMPERE, position, orientation);
|
||||
public Amperemeter(@NonNull final Point position) {
|
||||
super(MeterType.AMPERE, position);
|
||||
}
|
||||
|
||||
public Amperemeter(@NonNull final MeterDto dto) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.Getter;
|
||||
@ -11,6 +10,7 @@ import lombok.ToString;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.ph87.electro.CONFIG.*;
|
||||
import static de.ph87.electro.circuit.CircuitPainter.*;
|
||||
@ -18,7 +18,7 @@ import static java.lang.Math.round;
|
||||
|
||||
@Getter
|
||||
@ToString(callSuper = true, onlyExplicitlyIncluded = true)
|
||||
public class Battery extends Part implements CurrentSource {
|
||||
public class Battery extends Part {
|
||||
|
||||
public static final double DEFAULT_INNER_RESISTANCE = 0.05;
|
||||
|
||||
@ -49,8 +49,8 @@ public class Battery extends Part implements CurrentSource {
|
||||
@ToString.Include
|
||||
private double voltage = DEFAULT_VOLTAGE;
|
||||
|
||||
public Battery(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("Batterie", position, orientation);
|
||||
public Battery(@NonNull final Point position) {
|
||||
super("Batterie", position);
|
||||
minus = addNode("MINUS", P10, P50);
|
||||
plus = addNode("PLUS", P90, P50);
|
||||
innerResistance = new Resistance(minus, plus, DEFAULT_INNER_RESISTANCE);
|
||||
@ -64,6 +64,14 @@ public class Battery extends Part implements CurrentSource {
|
||||
innerResistance = new Resistance(minus, plus, dto.getResistance());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Stream<Battery> filterCast(@NonNull final Part part) {
|
||||
if (part instanceof final Battery battery) {
|
||||
return Stream.of(battery);
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _render(@NonNull final Graphics2D g) {
|
||||
drawLine(g, P10, P50, P50 - GAP / 2 - MINUS_W / 2, P50, Color.BLACK, SYMBOL_STROKE);
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.awt.*;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import static de.ph87.electro.CONFIG.*;
|
||||
import static de.ph87.electro.circuit.CircuitPainter.*;
|
||||
import static java.lang.Math.round;
|
||||
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
public class Capacitor extends Part implements CurrentSource {
|
||||
|
||||
private static final int MINUS_W = (int) round(0.1 * RASTER);
|
||||
|
||||
private static final int GAP = (int) round(0.05 * RASTER);
|
||||
|
||||
private static final int PLUS_W = (int) round(0.02 * RASTER);
|
||||
|
||||
private static final int PLUS_H = (int) round(0.6 * RASTER);
|
||||
|
||||
private final Point size = new Point(RASTER, RASTER);
|
||||
|
||||
private final Node plus;
|
||||
|
||||
private final Node minus;
|
||||
|
||||
private final Resistance innerResistance;
|
||||
|
||||
private double capacityFarad = 1;
|
||||
|
||||
private double chargeCoulomb = capacityFarad * 3;
|
||||
|
||||
private double innerVoltage = chargeCoulomb / capacityFarad;
|
||||
|
||||
private double outerVoltage;
|
||||
|
||||
private boolean charging = false;
|
||||
|
||||
public Capacitor(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("Kondensator", position, orientation);
|
||||
minus = addNode("-", P10, P50);
|
||||
plus = addNode("+", P90, P50);
|
||||
innerResistance = new Resistance(plus, minus, RESISTANCE_MAX);
|
||||
}
|
||||
|
||||
public Capacitor(@NonNull final LightDto dto) {
|
||||
super(dto);
|
||||
minus = addNode(dto.getB(), P10, P50);
|
||||
plus = addNode(dto.getA(), P90, P50);
|
||||
capacityFarad = 0;
|
||||
innerResistance = new Resistance(plus, minus, dto.getResistance());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCalculate(@NonNull final Duration dt) {
|
||||
outerVoltage = plus.getVoltage() - minus.getVoltage();
|
||||
|
||||
final double timeSeconds = dt.toNanos() / 1_000_000_000.0;
|
||||
final double current = innerResistance.getCurrent();
|
||||
if (!Double.isNaN(outerVoltage) && innerVoltage < outerVoltage) {
|
||||
innerResistance.setResistance(RESISTANCE_MIN);
|
||||
final double resistance = outerVoltage / current;
|
||||
final double targetCharge = capacityFarad * outerVoltage;
|
||||
final double deltaCharge = (targetCharge - chargeCoulomb) * (timeSeconds / (timeSeconds + RC(resistance)));
|
||||
chargeCoulomb += deltaCharge;
|
||||
} else {
|
||||
innerResistance.setResistance(RESISTANCE_MAX);
|
||||
chargeCoulomb += current * timeSeconds;
|
||||
if (chargeCoulomb < 0) {
|
||||
chargeCoulomb = 0;
|
||||
}
|
||||
}
|
||||
innerVoltage = chargeCoulomb / capacityFarad;
|
||||
charging = !Double.isNaN(outerVoltage) && innerVoltage < outerVoltage;
|
||||
}
|
||||
|
||||
public double getVoltage() {
|
||||
if (!Double.isNaN(plus.getVoltage()) && !Double.isNaN(minus.getVoltage())) {
|
||||
if (innerVoltage < outerVoltage) {
|
||||
return outerVoltage;
|
||||
}
|
||||
}
|
||||
return innerVoltage;
|
||||
}
|
||||
|
||||
private double RC(final double resistance) {
|
||||
return resistance * capacityFarad;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _render(@NonNull final Graphics2D g) {
|
||||
drawLine(g, P10, P50, P50 - GAP / 2 - MINUS_W / 2, P50, Color.BLACK, SYMBOL_STROKE);
|
||||
drawLine(g, P50 + GAP / 2 + PLUS_W / 2, P50, P90, P50, Color.BLACK, SYMBOL_STROKE);
|
||||
fillRect(g, P50 - MINUS_W - GAP / 2, P50 - PLUS_H / 2, MINUS_W, PLUS_H, Color.BLACK);
|
||||
fillRect(g, P50 + GAP / 2, P50 - PLUS_H / 2, MINUS_W, PLUS_H, Color.BLACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _labels(@NonNull final Graphics2D g) {
|
||||
drawText(g, LABEL_FONT, "%.1fC %.1fV".formatted(chargeCoulomb, getVoltage()), P50, P10, Color.BLACK, orientation);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public java.util.List<Resistance> getResistances() {
|
||||
return List.of(innerResistance);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.Getter;
|
||||
@ -21,8 +20,8 @@ public class Connector extends Part {
|
||||
@NonNull
|
||||
private final Point size = new Point(SUB_RASTER, SUB_RASTER);
|
||||
|
||||
public Connector(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("", position, orientation);
|
||||
public Connector(@NonNull final Point position) {
|
||||
super("", position);
|
||||
node = addNode("", SUB_RASTER / 2, SUB_RASTER / 2);
|
||||
}
|
||||
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface CurrentSource {
|
||||
|
||||
Node getPlus();
|
||||
|
||||
Node getMinus();
|
||||
|
||||
double getVoltage();
|
||||
|
||||
Resistance getInnerResistance();
|
||||
|
||||
@NonNull
|
||||
static Stream<CurrentSource> filterCast(@NonNull final Part part) {
|
||||
if (part instanceof final CurrentSource currentSource) {
|
||||
return Stream.of(currentSource);
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,7 +9,6 @@ import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.awt.*;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@ -44,8 +43,8 @@ public class Light extends Part {
|
||||
|
||||
private Color color = BULB_OFF_COLOR;
|
||||
|
||||
public Light(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("Licht", position, orientation);
|
||||
public Light(@NonNull final Point position) {
|
||||
super("Licht", position);
|
||||
a = addNode("A", P10, P50);
|
||||
b = addNode("B", P90, P50);
|
||||
innerResistance = new Resistance(a, b, DEFAULT_INNER_RESISTANCE);
|
||||
@ -66,7 +65,7 @@ public class Light extends Part {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCalculate(@NonNull final Duration dt) {
|
||||
public void postCalculate() {
|
||||
potentialDifference = abs(b.getVoltage() - a.getVoltage());
|
||||
defect |= potentialDifference > maxVoltage;
|
||||
if (defect) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import de.ph87.electro.common.RotationMatrix;
|
||||
@ -18,7 +17,6 @@ import static de.ph87.electro.CONFIG.*;
|
||||
import static de.ph87.electro.circuit.CircuitPainter.*;
|
||||
import static java.lang.Math.round;
|
||||
|
||||
@Getter
|
||||
public abstract class Meter extends Part {
|
||||
|
||||
private static final int FINGER_LENGTH = P65;
|
||||
@ -33,6 +31,7 @@ public abstract class Meter extends Part {
|
||||
|
||||
public final MeterType type;
|
||||
|
||||
@Getter
|
||||
public final Point size = new Point(RASTER, RASTER);
|
||||
|
||||
public final Node a;
|
||||
@ -49,8 +48,8 @@ public abstract class Meter extends Part {
|
||||
@Setter
|
||||
public double max;
|
||||
|
||||
public Meter(@NonNull MeterType type, @NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super(type.name, position, orientation);
|
||||
public Meter(@NonNull MeterType type, @NonNull final Point position) {
|
||||
super(type.name, position);
|
||||
this.type = type;
|
||||
this.unit = type.unit;
|
||||
this.min = type.min;
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.Getter;
|
||||
@ -40,8 +39,8 @@ public class Poti extends Part {
|
||||
|
||||
private double ratio = 0.0;
|
||||
|
||||
public Poti(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("Poti", position, orientation);
|
||||
public Poti(@NonNull final Point position) {
|
||||
super("Poti", position);
|
||||
common = addNode("C", P10, P50);
|
||||
middle = addNode("M", P50, P10);
|
||||
end = addNode("E", P90, P50);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.Getter;
|
||||
@ -27,18 +26,15 @@ public class Switch1x1 extends Part {
|
||||
|
||||
private final Node output;
|
||||
|
||||
private final Resistance resistanceOpen;
|
||||
|
||||
private final Resistance resistanceClosed;
|
||||
private final Resistance innerResistance;
|
||||
|
||||
private boolean state = false;
|
||||
|
||||
public Switch1x1(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("Ausschalter", position, orientation);
|
||||
public Switch1x1(@NonNull final Point position) {
|
||||
super("Ausschalter", position);
|
||||
common = addNode("C", P10, P50);
|
||||
output = addNode("O", P90, P50);
|
||||
resistanceOpen = new Resistance(common, output, RESISTANCE_MAX);
|
||||
resistanceClosed = new Resistance(common, output, RESISTANCE_MIN);
|
||||
innerResistance = new Resistance(common, output, RESISTANCE_MIN);
|
||||
}
|
||||
|
||||
public Switch1x1(@NonNull final Switch1x1Dto dto) {
|
||||
@ -46,8 +42,7 @@ public class Switch1x1 extends Part {
|
||||
common = addNode(dto.getCommon(), P10, P50);
|
||||
output = addNode(dto.getOutput(), P90, P50);
|
||||
state = dto.isState();
|
||||
resistanceOpen = new Resistance(common, output, RESISTANCE_MAX);
|
||||
resistanceClosed = new Resistance(common, output, RESISTANCE_MIN);
|
||||
innerResistance = new Resistance(common, output, RESISTANCE_MIN);
|
||||
}
|
||||
|
||||
public void setState(@NonNull final boolean state) {
|
||||
@ -72,9 +67,8 @@ public class Switch1x1 extends Part {
|
||||
@Override
|
||||
public List<Resistance> getResistances() {
|
||||
if (state) {
|
||||
return List.of(resistanceClosed);
|
||||
return List.of(innerResistance);
|
||||
}
|
||||
// return List.of(resistanceOpen);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.Getter;
|
||||
@ -32,8 +31,8 @@ public class Switch1x2 extends Part {
|
||||
|
||||
private boolean state = false;
|
||||
|
||||
public Switch1x2(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("Wechselschalter", position, orientation);
|
||||
public Switch1x2(@NonNull final Point position) {
|
||||
super("Wechselschalter", position);
|
||||
common = addNode("C", P10, P50);
|
||||
output0 = addNode("O0", P90, P25);
|
||||
output1 = addNode("O1", P90, P75);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Resistance;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import lombok.Getter;
|
||||
@ -38,8 +37,8 @@ public class SwitchCross extends Part {
|
||||
|
||||
private boolean state = false;
|
||||
|
||||
public SwitchCross(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super("Kreuzschalter", position, orientation);
|
||||
public SwitchCross(final Point position) {
|
||||
super("Kreuzschalter", position);
|
||||
common0 = addNode("C0", P10, P25);
|
||||
common1 = addNode("C1", P10, P75);
|
||||
output0 = addNode("O0", P90, P25);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
@ -9,8 +8,8 @@ import java.awt.*;
|
||||
@Getter
|
||||
public class Voltmeter extends Meter {
|
||||
|
||||
public Voltmeter(@NonNull final Point position, @NonNull final Orientation orientation) {
|
||||
super(MeterType.VOLT, position, orientation);
|
||||
public Voltmeter(@NonNull final Point position) {
|
||||
super(MeterType.VOLT, position);
|
||||
}
|
||||
|
||||
public Voltmeter(@NonNull final MeterDto dto) {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.ph87.electro.sidebar;
|
||||
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.parts.*;
|
||||
|
||||
@ -23,14 +22,14 @@ public class Sidebar extends JPanel {
|
||||
addToggle("Spannungen", () -> SHOW_NODE_VOLTAGES, v -> SHOW_NODE_VOLTAGES = v);
|
||||
|
||||
final Point ZERO = new Point();
|
||||
addPart(new Battery(ZERO, Orientation.R0));
|
||||
addPart(new Light(ZERO, Orientation.R0));
|
||||
addPart(new Switch1x1(ZERO, Orientation.R0));
|
||||
addPart(new Switch1x2(ZERO, Orientation.R0));
|
||||
addPart(new SwitchCross(ZERO, Orientation.R0));
|
||||
addPart(new Poti(ZERO, Orientation.R0));
|
||||
addPart(new Voltmeter(ZERO, Orientation.R0));
|
||||
addPart(new Amperemeter(ZERO, Orientation.R0));
|
||||
addPart(new Battery(ZERO));
|
||||
addPart(new Light(ZERO));
|
||||
addPart(new Switch1x1(ZERO));
|
||||
addPart(new Switch1x2(ZERO));
|
||||
addPart(new SwitchCross(ZERO));
|
||||
addPart(new Poti(ZERO));
|
||||
addPart(new Voltmeter(ZERO));
|
||||
addPart(new Amperemeter(ZERO));
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit;
|
||||
|
||||
import de.ph87.electro.circuit.calculation.Calculation;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import de.ph87.electro.circuit.part.parts.Battery;
|
||||
import de.ph87.electro.circuit.part.parts.Light;
|
||||
@ -9,8 +8,6 @@ import de.ph87.electro.circuit.wire.Wire;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static de.ph87.electro.CONFIG.RASTER;
|
||||
|
||||
@Slf4j
|
||||
@ -19,12 +16,12 @@ class CalculationServiceTest {
|
||||
@Test
|
||||
void test() {
|
||||
final Circuit circuit = new Circuit();
|
||||
final Battery battery = circuit.addPart(new Battery(RASTER(0, 0), Orientation.R0));
|
||||
final Light light = circuit.addPart(new Light(RASTER(0, 1), Orientation.R0));
|
||||
final Battery battery = circuit.addPart(new Battery(RASTER(0, 0)));
|
||||
final Light light = circuit.addPart(new Light(RASTER(0, 1)));
|
||||
circuit.connect(battery.getMinus(), light.getA());
|
||||
circuit.connect(battery.getPlus(), light.getB());
|
||||
|
||||
for (final Calculation calculation : Calculation.calculate(circuit, Duration.ofNanos(1))) {
|
||||
for (final Calculation calculation : Calculation.calculate(circuit)) {
|
||||
calculation.toString().lines().forEach(log::info);
|
||||
circuit.streamParts().forEach(part -> {
|
||||
log.info("");
|
||||
|
||||
@ -2,9 +2,8 @@ package de.ph87.electro.circuit.io;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.CircuitIOService;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.node.Node;
|
||||
import de.ph87.electro.circuit.part.Part;
|
||||
import de.ph87.electro.circuit.part.parts.Battery;
|
||||
import de.ph87.electro.circuit.part.parts.Light;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -24,8 +23,8 @@ class CircuitIOServiceTest {
|
||||
@Test
|
||||
void serialization() throws IOException {
|
||||
final Circuit circuit = new Circuit();
|
||||
final Battery battery = circuit.addPart(new Battery(RASTER(0, 0), Orientation.R0));
|
||||
final Light light = circuit.addPart(new Light(RASTER(0, 1), Orientation.R0));
|
||||
final Battery battery = circuit.addPart(new Battery(RASTER(0, 0)));
|
||||
final Light light = circuit.addPart(new Light(RASTER(0, 1)));
|
||||
circuit.connect(battery.getPlus(), light.getB());
|
||||
circuit.connect(light.getA(), battery.getMinus());
|
||||
check(circuit);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -15,9 +14,9 @@ public class BatteryLightTest {
|
||||
|
||||
private static final Circuit CIRCUIT = new Circuit();
|
||||
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0), Orientation.R0));
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0)));
|
||||
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(0, 1), Orientation.R0));
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(0, 1)));
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -16,11 +15,11 @@ public class BatterySwitcher1x1Test {
|
||||
|
||||
private static final Circuit CIRCUIT = new Circuit();
|
||||
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0), Orientation.R0));
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0)));
|
||||
|
||||
private static final Switch1x1 switcher = CIRCUIT.addPart(new Switch1x1(RASTER(0, 1), Orientation.R0));
|
||||
private static final Switch1x1 switcher = CIRCUIT.addPart(new Switch1x1(RASTER(0, 1)));
|
||||
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(1, 0), Orientation.R0));
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(1, 0)));
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -16,13 +15,13 @@ public class BatterySwitcher1x2Test {
|
||||
|
||||
private static final Circuit CIRCUIT = new Circuit();
|
||||
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0), Orientation.R0));
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0)));
|
||||
|
||||
private static final Switch1x2 switcher = CIRCUIT.addPart(new Switch1x2(RASTER(0, 2), Orientation.R0));
|
||||
private static final Switch1x2 switcher = CIRCUIT.addPart(new Switch1x2(RASTER(0, 2)));
|
||||
|
||||
private static final Light light0 = CIRCUIT.addPart(new Light(RASTER(1, 1), Orientation.R0));
|
||||
private static final Light light0 = CIRCUIT.addPart(new Light(RASTER(1, 1)));
|
||||
|
||||
private static final Light light1 = CIRCUIT.addPart(new Light(RASTER(1, 3), Orientation.R0));
|
||||
private static final Light light1 = CIRCUIT.addPart(new Light(RASTER(1, 3)));
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -16,13 +15,13 @@ public class BatterySwitcher2x2Test {
|
||||
|
||||
private static final Circuit CIRCUIT = new Circuit();
|
||||
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0), Orientation.R0));
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0)));
|
||||
|
||||
private static final Switch1x2 switcher0 = CIRCUIT.addPart(new Switch1x2(RASTER(0, 1), Orientation.R0));
|
||||
private static final Switch1x2 switcher0 = CIRCUIT.addPart(new Switch1x2(RASTER(0, 1)));
|
||||
|
||||
private static final Switch1x2 switcher1 = CIRCUIT.addPart(new Switch1x2(RASTER(1, 1), Orientation.R0));
|
||||
private static final Switch1x2 switcher1 = CIRCUIT.addPart(new Switch1x2(RASTER(1, 1)));
|
||||
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(1, 0), Orientation.R0));
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(1, 0)));
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.ph87.electro.circuit.part.parts;
|
||||
|
||||
import de.ph87.electro.circuit.Circuit;
|
||||
import de.ph87.electro.circuit.part.Orientation;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -16,15 +15,15 @@ public class BatterySwitcherCrossTest {
|
||||
|
||||
private static final Circuit CIRCUIT = new Circuit();
|
||||
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0), Orientation.R0));
|
||||
private static final Battery battery = CIRCUIT.addPart(new Battery(RASTER(0, 0)));
|
||||
|
||||
private static final Switch1x2 switcher0 = CIRCUIT.addPart(new Switch1x2(RASTER(0, 1), Orientation.R0));
|
||||
private static final Switch1x2 switcher0 = CIRCUIT.addPart(new Switch1x2(RASTER(0, 1)));
|
||||
|
||||
private static final SwitchCross switcherX = CIRCUIT.addPart(new SwitchCross(RASTER(1, 1), Orientation.R0));
|
||||
private static final SwitchCross switcherX = CIRCUIT.addPart(new SwitchCross(RASTER(1, 1)));
|
||||
|
||||
private static final Switch1x2 switcher1 = CIRCUIT.addPart(new Switch1x2(RASTER(2, 1), Orientation.R0));
|
||||
private static final Switch1x2 switcher1 = CIRCUIT.addPart(new Switch1x2(RASTER(2, 1)));
|
||||
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(2, 0), Orientation.R0));
|
||||
private static final Light light = CIRCUIT.addPart(new Light(RASTER(2, 0)));
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user