diff --git a/main.py b/main.py index 6e785b3..7d41564 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,18 @@ import pandas as pd import numpy as np -from controllers.validation import Validation +from model.validation import Validation +from model.reader import Reader +from model.solver import Solver + +from model.impedances import Resistor +from model.impedances import Inductor +from model.impedances import Capacitor + +from model.source import Voltage +from model.source import Current + +from model.node import Node def stop(message): print('Exiting simulation with message: ' + message) @@ -11,9 +22,9 @@ def stop(message): json = 'C:\emtpython\system.json' validator = Validation(json) +reader = Reader(json) # validating components - nameValid = validator.validateName() parametersValid = validator.validateParameters() nodesValid = validator.validateNodes() @@ -36,15 +47,25 @@ def stop(message): stop(sourcesValid[1]) # verifing repeated connections - idsChecked = validator.checkIds() if not idsChecked[0]: stop(idsChecked[1]) -# proccessing input data +# processing input data # transforming json in objects according to existing models +reader.readNodes() +reader.readCircuits() +reader.readSources() +reader.convertSources() + +reader.log() + +# solving the circuit +solver = Solver(reader.data) + +solver.steadyState() # ending simulation stop('Simulation finished succesfully!') diff --git a/model/circuit.py b/model/circuit.py index 0101e0e..aa60cc7 100644 --- a/model/circuit.py +++ b/model/circuit.py @@ -20,3 +20,6 @@ def __init__(self, name, id, f_id, t_id): self.t_id = t_id # to node identificator # the get and set methods were left to Python to handle + + def log(self): + print('Circuit with id: ' + self.id + '; name: ' + self.name + '; from: ' + self.f_id + '; to: ' + self.t_id) diff --git a/model/impedances.py b/model/impedances.py index 906811b..ba44b61 100644 --- a/model/impedances.py +++ b/model/impedances.py @@ -4,22 +4,58 @@ An impedance is a circuit which opposes to the flow of current. ''' -from circuit import Circuit +from model.circuit import Circuit class Resistor(Circuit): def __init__(self, name, id, f_id, t_id, resistance): - super().__init__(self, name, id, f_id, t_id) + # data checking + if not isinstance(name, str) or not isinstance(id, str) or not isinstance(f_id, str) or not isinstance(t_id, str): + raise Exception('Parameters name, id, f_id and t_id must be strings ONLY.') + + self.name = name # freely user defined circuit public name + self.id = id # exclusive identificator (handled elsewhere) + self.f_id = f_id # from node identificator + self.t_id = t_id # to node identificator self.resistance = resistance -class Resistor(Circuit): + # method to create an instance from json object + @staticmethod + def json(key, value): + return Resistor(value['name'], key, value['f_id'], value['t_id'], value['resistance']) + +class Inductor(Circuit): def __init__(self, name, id, f_id, t_id, inductance): - super().__init__(self, name, id, f_id, t_id) + # data checking + if not isinstance(name, str) or not isinstance(id, str) or not isinstance(f_id, str) or not isinstance(t_id, str): + raise Exception('Parameters name, id, f_id and t_id must be strings ONLY.') + + self.name = name # freely user defined circuit public name + self.id = id # exclusive identificator (handled elsewhere) + self.f_id = f_id # from node identificator + self.t_id = t_id # to node identificator self.inductance = inductance -class Capacitance(Circuit): + # method to create an instance from json object + @staticmethod + def json(key, value): + return Inductor(value['name'], key, value['f_id'], value['t_id'], value['inductance']) + +class Capacitor(Circuit): def __init__(self, name, id, f_id, t_id, capacitance): - super().__init__(self, name, id, f_id, t_id) + # data checking + if not isinstance(name, str) or not isinstance(id, str) or not isinstance(f_id, str) or not isinstance(t_id, str): + raise Exception('Parameters name, id, f_id and t_id must be strings ONLY.') + + self.name = name # freely user defined circuit public name + self.id = id # exclusive identificator (handled elsewhere) + self.f_id = f_id # from node identificator + self.t_id = t_id # to node identificator self.capacitance = capacitance + + # method to create an instance from json object + @staticmethod + def json(key, value): + return Capacitor(value['name'], key, value['f_id'], value['t_id'], value['capacitance']) diff --git a/model/node.py b/model/node.py index fa060d6..9be57ba 100644 --- a/model/node.py +++ b/model/node.py @@ -16,9 +16,17 @@ def __init__(self, name, id, circuits = []): self.id = id # exclusive identificator (handled elsewhere) self.circuits = circuits # connected circuits identifiers + def log(self): + print('Node with id: ' + self.id + '; name: ' + self.name + '; circuits: ' + str(len(self.circuits))) + # the get and set methods were left for Python to handle # method to create a ground node @staticmethod def ground(): return Node('ground', '0') + + # method to create an instance from json object + @staticmethod + def json(key, value): + return Node(value['name'], key, value['circuits']) diff --git a/model/reader.py b/model/reader.py new file mode 100644 index 0000000..0dba943 --- /dev/null +++ b/model/reader.py @@ -0,0 +1,102 @@ +# Input json file reading in programs format + +import json as js + +from model.impedances import Resistor +from model.impedances import Inductor +from model.impedances import Capacitor + +from model.source import Voltage +from model.source import Current + +from model.node import Node + +Rth = 1.E-30 + +class Reader: + + # the json file must be valid, the validator does this process + def __init__(self, json): + # the parameter json is the json file path + try: + with open(json, 'r') as f: + self.json = js.load(f) + self.data = { + 'stop': self.json['parameters']['stop'], + 'dT': self.json['parameters']['dT'], + 'frequency': self.json['parameters']['frequency'] + } # consolidated dictionary with converted objects + except Exception as error: + print(error) + self.json = {} + + def readNodes(self): + # reads the components in node + nodes = self.json['nodes'] + self.data['nodes'] = [] + for node in nodes: + self.data['nodes'].append(Node.json(node, nodes[node])) + print('Nodes processed: ') + for node in self.data['nodes']: + node.log() + + def readCircuits(self): + # reads the components in circuits + circuits = self.json['circuits'] + self.data['circuits'] = [] + for circuit in circuits: + if 'resistance' in circuits[circuit]: + self.data['circuits'].append(Resistor.json(circuit, circuits[circuit])) + if 'inductance' in circuits[circuit]: + self.data['circuits'].append(Inductor.json(circuit, circuits[circuit])) + if 'capacitance' in circuits[circuit]: + self.data['circuits'].append(Capacitor.json(circuit, circuits[circuit])) + print('Circuits processed: ') + for circuit in self.data['circuits']: + circuit.log() + + def readSources(self): + # reads the components in sources + frequency = self.json['parameters']['frequency'] + sources = self.json['sources'] + self.data['sources'] = [] + for source in sources: + if 'voltage' in sources[source]: + self.data['sources'].append(Voltage.json(source, sources[source], frequency)) + if 'current' in sources[source]: + self.data['sources'].append(Current.json(source, sources[source], frequency)) + print('Sources processed: ') + for source in self.data['sources']: + source.log() + + # preparing sources for simulation, converting all voltage sources into current sources + def convertSources(self): + for i in range(len(self.data['sources'])): + source = self.data['sources'][i] + if str(type(source).__name__) == 'Voltage': + source = Current(source.name, source.id, source.f_id, source.t_id, source.voltage / Rth, source.angle, source.frequency) + thevenin = Resistor('fictional thevenin', 'th <> ' + source.id, source.f_id, source.t_id, Rth) + self.data['circuits'].append(thevenin) + if source.f_id != '0': + for node in self.data['nodes']: + if node.id == source.f_id: + node.circuits.append(thevenin.id) + if source.t_id != '0': + for node in self.data['nodes']: + if node.id == source.t_id: + node.circuits.append(thevenin.id) + self.data['sources'][i] = source + # print(source.current) + print('Sources converted!') + # print(self.data) + + # presenting systems consolidated data + def log(self): + print('\nSystem data: ') + print('Frequency: ' + str(self.data['frequency'])) + for node in self.data['nodes']: + node.log() + for circuit in self.data['circuits']: + circuit.log() + for source in self.data['sources']: + source.log() diff --git a/model/solver.py b/model/solver.py new file mode 100644 index 0000000..adc6d0a --- /dev/null +++ b/model/solver.py @@ -0,0 +1,69 @@ +# this one effectivelly solves the circuit + +import numpy as np +import math + +class Solver: + + def __init__(self, data): + self.data = data + + # steady-state solution + def steadyState(self): + # creating G matrix and Z matrix + G = [] + nodes = self.data['nodes'] + circuits = self.data['circuits'] + for i in range(len(nodes)): + line = [] + for j in range(len(nodes)): + m = nodes[i].id + n = nodes[j].id + # if m == n, Gmn is the sum of the susceptances connected to node m/n + if m == n: + g = 0 + for circuit in circuits: + if circuit.f_id == m or circuit.t_id == m: + if str(type(circuit).__name__) == 'Resistor': + g = g + 1 / circuit.resistance + if str(type(circuit).__name__) == 'Inductor': + g = g + 1 / (1j * 2 * math.pi * self.data['frequency'] * circuit.inductance) + if str(type(circuit).__name__) == 'Capacitor': + g = g + (1j * 2 * math.pi * self.data['frequency'] * circuit.capacitance) + line.append(g) + else: + g = 0 + for circuit in circuits: + if (circuit.f_id == m and circuit.t_id == n) or (circuit.f_id == n and circuit.t_id == m): + if str(type(circuit).__name__) == 'Resistor': + g = g + 1 / circuit.resistance + if str(type(circuit).__name__) == 'Inductor': + g = g + 1 / (1j * 2 * math.pi * self.data['frequency'] * circuit.inductance) + if str(type(circuit).__name__) == 'Capacitor': + g = g + (1j * 2 * math.pi * self.data['frequency'] * circuit.capacitance) + line.append(-g) + G.append(line) + G = np.matrix(G) + Z = G.I + # creating I matrix + I = [] + sources = self.data['sources'] # assuming the conversion to current sources + for i in range(len(nodes)): + m = nodes[i].id + current = 0 + for source in sources: + if source.f_id == m: + current = current + source.phasor() + if source.t_id == m: + current = current - source.phasor() + I.append(current) + I = np.matrix(I) + I = I.transpose() + V = Z*I + voltages = [] + for line in V: + for v in line: + voltage = v[0, 0] + voltage = (math.sqrt(np.real(voltage) ** 2 + np.imag(voltage) ** 2), math.atan(np.imag(voltage) / np.real(voltage)) * 180 / math.pi) + voltages.append(voltage) + print(voltages) diff --git a/model/source.py b/model/source.py index be81312..428c09a 100644 --- a/model/source.py +++ b/model/source.py @@ -5,13 +5,14 @@ difference and a current is able to flow actively. ''' -from circuit import Circuit +from model.circuit import Circuit +import math class Voltage(Circuit): def __init__(self, name, id, f_id, t_id, voltage, angle, frequency): # data checking - if not isinstance(name, str) or not isinstance(id, str) or not isinstance(f_id, str) or not isinstance(t_id, str) or not isinstance(voltage, flt) or not isinstance(angle, flt) or not isinstance(frequency, flt): + if not isinstance(name, str) or not isinstance(id, str) or not isinstance(f_id, str) or not isinstance(t_id, str) or not isinstance(voltage, float) or not isinstance(angle, float) or not isinstance(frequency, float): raise Exception('Parameters name, id, f_id and t_id must be strings ONLY and parameters voltage, angle and frequency must be FLOAT only.') # data definition self.name = name # freely user defined circuit public name @@ -22,11 +23,19 @@ def __init__(self, name, id, f_id, t_id, voltage, angle, frequency): self.angle = angle # nominal angle in degrees self.frequency = frequency # frequency in Hertz + def phasor(self): + return self.voltage * math.cos(self.angle * math.pi / 180.) + 1j * self.voltage * math.sin(self.angle * math.pi / 180.) + + # method to create an instance from json object + @staticmethod + def json(key, value, frequency): + return Voltage(value['name'], key, value['f_id'], value['t_id'], value['voltage'], value['angle'], frequency) + class Current(Circuit): def __init__(self, name, id, f_id, t_id, current, angle, frequency): # data checking - if not isinstance(name, str) or not isinstance(id, str) or not isinstance(f_id, str) or not isinstance(t_id, str) or not isinstance(current, flt) or not isinstance(angle, flt) or not isinstance(frequency, flt): + if not isinstance(name, str) or not isinstance(id, str) or not isinstance(f_id, str) or not isinstance(t_id, str) or not isinstance(current, float) or not isinstance(angle, float) or not isinstance(frequency, float): raise Exception('Parameters name, id, f_id and t_id must be strings ONLY and parameters current, angle and frequency must be FLOAT only.') # data definition self.name = name # freely user defined circuit public name @@ -36,3 +45,11 @@ def __init__(self, name, id, f_id, t_id, current, angle, frequency): self.current = current # nominal PEAK current in Amps self.angle = angle # nominal angle in degrees self.frequency = frequency # frequency in Hertz + + def phasor(self): + return self.current * math.cos(self.angle * math.pi / 180.) + 1j * self.current * math.sin(self.angle * math.pi / 180.) + + # method to create an instance from json object + @staticmethod + def json(key, value, frequency): + return Current(value['name'], key, value['f_id'], value['t_id'], value['current'], value['angle'], frequency) diff --git a/controllers/validation.py b/model/validation.py similarity index 97% rename from controllers/validation.py rename to model/validation.py index 831321d..48f740c 100644 --- a/controllers/validation.py +++ b/model/validation.py @@ -7,8 +7,8 @@ class Validation: def __init__(self, json): # the parameter json is the json file path try: - f = open(json, 'r') - self.json = js.load(f) + with open(json, 'r') as f: + self.json = js.load(f) except Exception as error: print(error) self.json = {} @@ -38,6 +38,8 @@ def validateNodes(self): return (False, 'The field nodes is missing in the input file...') # nodes list empty or inexistent nodes = self.json['nodes'] for node in nodes: + if node == '0': + return (False, 'There is no need to create the ground node...') if 'name' not in nodes[node] or 'circuits' not in nodes[node]: return (False, 'One of the necessary fields of node is missing for node -> ' + node) # nodes fields missing if not isinstance(nodes[node]['name'], str): diff --git a/system.json b/system.json index fb8b6e6..6248765 100644 --- a/system.json +++ b/system.json @@ -3,7 +3,7 @@ "parameters": { "stop": 10.0, "dT": 0.00001, - "frequency": 60.0 + "frequency": 1.0 }, "nodes": { "1": { @@ -27,6 +27,12 @@ "f_id": "2", "t_id": "0", "inductance": 1.0 + }, + "capacitor": { + "name": "capacitor", + "f_id": "2", + "t_id": "0", + "capacitance": 0.00001 } }, "sources": {