Sunday, November 29, 2020

Isolated dc-dc converters - understanding the flow of energy

I have been creating videos on dc-dc converters for close to year. The entire playlist of videos can be found at:

https://www.youtube.com/playlist?list=PL-_jTul4we2TUk7zANWQhGNc6kkJ4hg5J

Typically, when learning power electronics as either an undergraduate or in the early stages of a master's, dc-dc converters are presented one after the other with very little talk about possible links between them or how one could progress from one to the other. During the course of this lecture series, I have been trying to do exactly that - focus on how the converters differ from one another in terms of how the energy flows.

As I have been teaching power electronics, I find that using equations alone to describe the operation of a converter is quite inadequate. Power electronic converters are nonlinear and therefore, equations rarely help to understand how they behave. Equations and analysis can help in designing and optimizing their performance but to understand how they work, it is necessary to understand how energy flows between the different states of the converter and how that fits in with the purpose of the converter.

In the last few weeks, I have been gradually approaching the flyback converter topology. The reason I say I have been gradually approaching this topology is because rather than just present the topology and simulate it, I felt it is necessary to describe how this topology came into being. So, instead of using the opposing winding dot polarity that is characteristic and fundamental in a flyback converter, I use the normal transformer where both winding have a dot polarity at the same (upper) terminal.

The simulation shows what happens when the magnetizing current in the transformer is broken. I show how it is possible to connect a LC filter at the secondary winding to produce a filtered output voltage and additionally how a freewheel diode can be used to ensure the continuity of current through the filter inductor, the main problem arises with the magnetic energy in the transformer having nowhere to go during one stage of the converter operation.

With almost every topology of isolated converters, the main question arises about how to ensure the energy stored in a magnetic component (inductor or transformer) increases and decreases during a switching cycle. If the energy continuously increases which implies the peak current keeps increasing, eventually the inductor or transformer will saturate as the flux corresponding to the peak will be greater than the knee-point of the B-H curve. Additionally, the changes need to happen cyclically and gradually - an increase in current should be following by a decrease.

In general, this is what makes power electronics fascinating - how one can condition power without moving parts but just by ensuring a cyclical flow of power. This is true for any power electronic converter.

Tuesday, October 27, 2020

Fourth Udemy course and second book

 It has been a very long time since I blogged. Quite sadly, my project of writing automatic tests for the circuit simulator has not progressed for several months.

I am in the process of creating my fourth online course for Udemy. This course will cover the basics of control and operation of grid converters. It will start with basic grid topologies, computations performed on grids - peak voltage computation, RMS value computation, frequency estimation. A basic overview of the PWM strategy backed by Fourier series harmonic content of the inverter output voltages will be covered. In terms of control, there will be a coverage of the principle of designing a converter, frequency response plots and how to use these to design controllers. A basic current controller will be designed using this basic technique. The current controller will be a simple PI controller in synchronous reference frame.

My second book will soon be in print. This book is titled Digital Filter Design for Power Engineering Applications using Python: An open source guide. The book is being published by Springer International. Hopefully, the online version will be available in December.

There are a few changes long pending with the circuit simulator. These might be done sometime early in 2021.

Tuesday, June 16, 2020

Third Udemy course and the road ahead

It has been a while since I blogged. Quite a lot has happened in the meantime. I created three online courses that are available on the MOOC website Udemy. They are available in the following links:
Simulating Power Electronic Circuits using Python:
https://www.udemy.com/course/simulating-power-electronic-circuits-using-python/?couponCode=A8E5404EE6DD73D31530

Basics of Digital Signal Processing for Power Engineers:
https://www.udemy.com/course/basics-of-digital-signal-processing-for-power-engineers/?couponCode=0DEFA2CAD031AB8DEEEA

Simulation of Magnetics for Power Electronics using Python:
https://www.udemy.com/course/simulation-of-magnetics-for-power-electronics-using-python/?couponCode=F3803A3E81906B554A6A

I am currently writing a book based on the course material of my second course on digital signal processing. I hope the first draft of the book to be ready by the end of the month.

The next steps will be courses on different topics in power electronics. To begin with, there will be a course on control of single-phase grid connected converters. In this I will go into complete details of how to control the current injected by a single-phase converter that could potentially be used for interfacing a PV panel or a battery or for any other purpose.

I have several courses planned for the future. The eventual objective is to establish an online power electronics university with continuously expanding courses that tackle the latest breakthroughs in power electronics. The objective is to take complex research topics and break them up into courses with simulations for students to be able to learn on their own pace.

A few tentative topics will be as follows. Since, my Master's thesis was on active filters, one course could be on shunt and series active filters in distribution systems. The course will cover techniques of injection and also provide simulation cases with converter topologies and closed loop control strategies. Another potential course will be on UPS and the applications in distributed systems. Going further, I will examine parallel-connected UPS systems to form a microgrid.

Once, I tackle the topic of microgrids, I will then examine integration of renewable energy sources such as a solar PV and wind turbines. I will examine how co-ordinated control of renewable energy sources can result in a smart grid.

To get quick updates on this project, like or follow my Facebook page:
https://www.facebook.com/pythonpowerelectronics
Or follow me on Twitter:
https://twitter.com/pythonpoweretrx

Tuesday, May 5, 2020

Next course on simulation of magnetics

It has been a while since I blogged. Nowadays I am not doing much dev with respect to the circuit simulator. Most of my time is spent in writing my next book on using Python for digital filtering for power engineering and in creating my next course on simulation of transformers using Python Power Electronics. So this blog will be about my writing and my next video course.

The book on digital signal processing using Python is based on the Udemy course Basics of Digital Signal Processing for Power Engineers:
https://www.udemy.com/course/basics-of-digital-signal-processing-for-power-engineers/

While creating the course, I put together the course material (what I would be talking about) for every lecture as just a text file. The idea was to build a story for the course - lecture by lecture, section by section. At the end of the course, I found that the text file was close to 100 pages. This was without any diagrams, simulation results or additional. It seemed a waste to this let this course material be as is as the course itself has become a bestseller on Udemy. So, I started putting this text file together as a book and so far have written four chapters and 117 pages. Another two chapters are remaining after which I will write the introduction and conclusion. And then the hunt for a publisher.

My main objective behind publishing is to write books that are easy to read and break hard-core engineering down into simple understandable text. Another major feature of the book is that all references will be open links - primarily Wikipedia. I am a supported of Wikipedia and use Wikipedia links heavily in my online teaching as well.

The next online course is based on magnetics primarily simulating transformers. Though it is a well reported topic, my reason for digging into it was that in terms of implementation, transformers and magnetics in general are tackled fairly heuristically - mainly by just trial and error. The objective of the course is to bring this entire topic down to basic physics - Faraday's Law, Lenz's, Ampere's Law. In the future, my advanced courses will use magnetics particularly multi-winding transformers and therefore, this course is an introduction to that.

Gradually, the process of creating video courses is becoming far easier and quite enjoyable. The initial agony in filtering out noise from videos is now a thing I laugh about. A decent noise canceling mic is fairly important for recording. My very first course was recorded with a mobile phone headset. I had to wrap myself in a blanket to shield myself from noise as it picked up everything not just in the apartment but in the entire neighbourhood. I had Audacity setup for amplifying audio and removing whatever little noise that remains. I still end up with a minor background hum but I would not worry about that.

With that said, I would like to see how long it takes to record my next course. I have recorded and uploaded 3 hours and 48 minutes of video lectures already in 5 days. At this rate, if all goes well, I should be done by the end of May.

Sunday, February 23, 2020

Philosophy behind the video lectures

It has been a while since I last blogged. Since I am way too busy right now with creating my next Udemy course, I haven't had time to blog about code. So I thought I would blog about the video series that I have been now consistently adding to on a weekly basis. In case you haven't seen any of them, this is the YouTube channel.
https://www.youtube.com/channel/UCxVbKNK18A_a9Ohd0Kb7kNA

The idea for this came from listening to a few podcasts. Initially, I thought of creating a podcast for power electronics, but then realized that I wouldn't be able to convey much just through audio as I usually like to code along. So, I decided with a weekly video on some topic in power electronics. Initially, I used to spend a lot of time preparing them, and for that reason, could only create a new video once in several weeks. But then, I watered it down a bit, and just recorded myself as I casually simulated and now am able to keep my weekly routine.

The main reason for these lectures was to provide a consistent flow of content for someone who wants to learn power electronics but is too busy. The target audience is someone who works as an electrical engineer, would like to level up with their skills to move on to being a design engineer but just can't find the time and due to financial and/or family constraints can't take a few years off to go back and do another degree.

For this reason, these lectures have a bit of theory but the main aspect is that someone who is interested can just code along. All software are free and open source and you could install every software in any operating system that you use. So, the idea is that a student could learn more than just equations from these video lectures.

A part of my philosophy behind creating these videos is similar to Richard Feynman's philosophy of teaching. Pick a topic. Teach it as if you were teaching it to a child who was hearing about it for the first time. Look for where your message didn't go through. Go back and simplify it further.

I have taken many courses on Udemy. One of the problems with adult education is that adults already have responsibilities. The traditional approach to giving homework and expecting them to work on it on their own time will not have much effect as time is what is lacking. So now that I have launched my own courses, I realize the need to make things explicit. And this goes back to Feynman's approach to teaching.

Making these videos has given me to opportunity to get better gradually. And I am enjoying creating videos as it also helps me to relearn what I might have conveniently forgotten.

Wednesday, January 8, 2020

Testing node and branch methods

I have tested the methods in determining nodes and branches. What is left is the main method that calls these methods. The code can be found on:
https://bitbucket.org/shivkiyer/ppe_simulator/src/testing/
https://sourceforge.net/p/pythonpowerelec/code/ci/testing/tree/

And if you would like a full length course on simulating power electronics using Python, check out:
https://www.udemy.com/course/simulating-power-electronic-circuits-using-python/

Here is the code for the tests in command line and web app:

class TestNodesBranches:
"""
This class tests functions related to processing nodes and
branches of a circuit.
"""
def test_node_checking(self):
"""
node_checking function determines if an element is a node by it's neighbors.
params - circuit matrix
params - sheet, row, column of the element.
params - number of rows and columns of that sheet.
result/modifies - list of nodes to which the element is added if it is a node.
"""
from network_reader import node_checking
print()
print('*'*80)
print('Testing the node_checking function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_list = []
no_rows = len(test_matrix[0])
no_cols = len(test_matrix[0][0])
no_of_nodes = 0
# The center wire is a legal node
node_checking(test_matrix, test_list, 0, 2, 2, no_rows, no_cols)
assert no_of_nodes != len(test_list)
assert test_list[-1] == [0, 2, 2]
no_of_nodes = len(test_list)
# The element below that is not a node
node_checking(test_matrix, test_list, 0, 1, 2, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# The element in the first row and second column is not a node
node_checking(test_matrix, test_list, 0, 0, 2, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Adding a dummy wire in element in first row, second column
test_matrix[0][0][1] = 'wire'
# STill not a node
node_checking(test_matrix, test_list, 0, 0, 2, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Adding another dummy element to right of it.
test_matrix[0][0][3] = 'wire'
# Now is a node
node_checking(test_matrix, test_list, 0, 0, 2, no_rows, no_cols)
assert no_of_nodes != len(test_list)
assert test_list[-1] == [0, 0, 2]
no_of_nodes = len(test_list)
# Corner element should never be a node
node_checking(test_matrix, test_list, 0, 0, 0, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Resetting the element in first row, second column
test_matrix[0][0][1] = ''
# Element in second row, second column should not be a node
node_checking(test_matrix, test_list, 0, 1, 1, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Placing a component in the element
test_matrix[0][0][1] = 'wire'
# Now the element in second row, second column is a node
node_checking(test_matrix, test_list, 0, 1, 1, no_rows, no_cols)
assert no_of_nodes != len(test_list)
assert test_list[-1] == [0, 1, 1]
no_of_nodes = len(test_list)
# Confirming that a corner element should never be a node
test_matrix[0][0][1] = 'wire'
test_matrix[0][1][0] = 'wire'
node_checking(test_matrix, test_list, 0, 0, 0, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Element in first row, third column should not a node
node_checking(test_matrix, test_list, 0, 2, 0, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Adding a component to the right of it.
test_matrix[0][3][0] = 'wire'
# Now is a node
node_checking(test_matrix, test_list, 0, 2, 0, no_rows, no_cols)
assert no_of_nodes != len(test_list)
assert test_list[-1] == [0, 2, 0]
no_of_nodes = len(test_list)
# Element in 4th row and 2nd column is a node
# This element is null. This method does not check for component.
# The parent method performs the check before calling this function.
node_checking(test_matrix, test_list, 0, 3, 1, no_rows, no_cols)
assert no_of_nodes != len(test_list)
assert test_list[-1] == [0, 3, 1]
no_of_nodes = len(test_list)
# Element in 4th row and 3rd column is not a node.
node_checking(test_matrix, test_list, 0, 3, 2, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Element in last row, 3rd column is not a node
node_checking(test_matrix, test_list, 0, 5, 2, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Adding an element to the left
test_matrix[0][5][1] = 'wire'
# Still not a node
node_checking(test_matrix, test_list, 0, 5, 2, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Adding an element to the right
test_matrix[0][5][3] = 'wire'
# Now a node
node_checking(test_matrix, test_list, 0, 5, 2, no_rows, no_cols)
assert no_of_nodes != len(test_list)
assert test_list[-1] == [0, 5, 2]
no_of_nodes = len(test_list)
# element in last column, 3rd row is not a node
node_checking(test_matrix, test_list, 0, 2, 4, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Setting element below it to be a component
test_matrix[0][1][4] = 'wire'
# Still not a node
node_checking(test_matrix, test_list, 0, 2, 4, no_rows, no_cols)
assert no_of_nodes == len(test_list)
# Setting element above it to be a component
test_matrix[0][3][4] = 'wire'
# Now a node
node_checking(test_matrix, test_list, 0, 2, 4, no_rows, no_cols)
assert no_of_nodes != len(test_list)
assert test_list[-1] == [0, 2, 4]
no_of_nodes = len(test_list)
print()
return
def test_jump_move(self):
"""
jump_move - function to return the next cell after encountering a jump
params - circuit matrix
params - dictionary of jumps with pairs of jumps, each jump - [[sheet, row, col], direction]
params - sheet, row, column of originating jump
params - position of destination jump in jump dictionary value list with the jump label as key.
returns - list - [direction, co-ordinates of destination cell after jump]
"""
from network_reader import jump_move
print()
print('*'*80)
print('Testing the jump_move function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
# First is a jump label that exists. Second is fictitious
temp_jump_matrix = {
'jump1': [[[0, 2, 4], 'left'], [[0, 2, 1], 'left']]
}
# Start jump is ok.
# Destination is not a jump. No exception raises. Next cell is an element.
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
assert result == ['left', 0, 2, 0]
# Start is not a jump - so exception raised as element is checked with jump dictionary.
with pytest.raises(Exception):
jump_move(test_matrix, temp_jump_matrix, [0, 2, 2], 1)
# Also ok.
temp_jump_matrix['jump1'][1][1] = 'right'
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
assert result == ['right', 0, 2, 2]
# Destination element is not a component.
temp_jump_matrix['jump1'][1][1] = 'up'
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
assert result == ['up', 0, 1, 1]
# Destination element is not a component.
temp_jump_matrix['jump1'][1][1] = 'down'
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
assert result == ['down', 0, 3, 1]
print()
return
def test_branch_jump(self):
"""
branch_jump takes an element, checks if it is a jump and
returns the element next to the corresponding jump if so
or else return back the element.
params - circuit matrix
params - jump matrix dictionary
params - current element
returns - list [direction, sheet, row, column]
"""
from network_reader import branch_jump
print()
print('*'*80)
print('Testing the branch_jump function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
# First is a jump label that exists. Second is fictitious
temp_jump_matrix = {
'jump1': [[[0, 2, 4], 'left'], [[0, 2, 1], 'left']]
}
# If the element is not a jump, jump executed is null (not executed)
# and same element is returned.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 3])
assert result == ['', 0, 2, 3]
# If element is jump, element next to corresponding jump is returned.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['left', 0, 2, 0]
# Also ok.
temp_jump_matrix['jump1'][1][1] = 'right'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['right', 0, 2, 2]
# Element does not exist. Error checking does not happen at this stage.
temp_jump_matrix['jump1'][1][1] = 'up'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['up', 0, 1, 1]
# Element does not exist. Error checking does not happen at this stage.
temp_jump_matrix['jump1'][1][1] = 'down'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['down', 0, 3, 1]
# Flipping the jump labels to check if method still maps jump labels.
temp_jump_matrix = {
'jump1': [ [[0, 2, 1], 'left'], [[0, 2, 4], 'left'] ]
}
# No jump, same behaviour.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 3])
assert result == ['', 0, 2, 3]
# Again no jump.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 1])
assert result == ['', 0, 2, 1]
# Legal jump
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['left', 0, 2, 0]
# Legal jump
temp_jump_matrix['jump1'][0][1] = 'right'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['right', 0, 2, 2]
# Element does not exist, no error.
temp_jump_matrix['jump1'][0][1] = 'up'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['up', 0, 1, 1]
# Element does not exist, no error.
temp_jump_matrix['jump1'][0][1] = 'down'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
assert result == ['down', 0, 3, 1]
print()
return
def test_branch_advance(self):
"""
branch_advance takes an element and returns the next element
to extend the branch checking whether the element already is
in the temporary branch or if a jump is executed.
params - circuit matrix
params - temporary branch
params - current element
params - jump executed
returns - [jump, next element]
"""
from network_reader import branch_advance
print()
print('*'*80)
print('Testing the branch_advance function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_branch_iter = [ [0, 2, 2] ]
# Since starting with a node that has elements on all 4 sides,
# First check is for an element to the left if no jump is specified.
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
assert result == ['', 0, 2, 1]
# Since left element is in temp branch, next check is to the top
test_branch_iter = [ [0, 2, 1], [0, 2, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
assert result == ['', 0, 1, 2]
# Since top element is in temp branch, next check is to the right
test_branch_iter = [ [0, 2, 1], [0, 2, 2], [0, 1, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
assert result == ['', 0, 2, 3]
# Since right element is in temp branch, next check is to the bottom
test_branch_iter = [ [0, 2, 1], [0, 2, 2], [0, 1, 2], [0, 2, 3] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
assert result == ['', 0, 3, 2]
# Since all adj elements are in temp branch, fails returning back node.
test_branch_iter = [ [0, 2, 1], [0, 2, 2], [0, 1, 2], [0, 2, 3], [0, 3, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
assert result == ['', 0, 2, 2]
# Extending branch to the right from node.
test_branch_iter = [ [0, 2, 2], [0, 2, 3] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], '')
assert result == ['', 0, 2, 4]
# If right element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 2, 3], [0, 2, 4] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], '')
assert result == ['', 0, 2, 3]
# Executing legal right jump to move right
test_branch_iter = [ [0, 2, 2], [0, 2, 3] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'right')
assert result == ['', 0, 2, 4]
# Invalid down jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'down')
assert result == ['', 0, 2, 4]
# Invalid up jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'up')
assert result == ['', 0, 2, 4]
# Illegal left jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'left')
assert result == ['left', 0, 2, 3]
# Moving left from the node
test_branch_iter = [ [0, 2, 2], [0, 2, 1] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], '')
assert result == ['', 0, 2, 0]
# If left element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 2, 1], [0, 2, 0] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], '')
assert result == ['', 0, 2, 1]
# Executing legal left jump to move left
test_branch_iter = [ [0, 2, 2], [0, 2, 1] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'left')
assert result == ['', 0, 2, 0]
# Invalid down jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'down')
assert result == ['', 0, 2, 0]
# Invalid up jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'up')
assert result == ['', 0, 2, 0]
# Illegal right jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'right')
assert result == ['right', 0, 2, 1]
# Moving up from the node
test_branch_iter = [ [0, 2, 2], [0, 1, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], '')
assert result == ['', 0, 0, 2]
# If up element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 1, 2], [0, 0, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], '')
assert result == ['', 0, 1, 2]
# Executing legal up jump to move left
test_branch_iter = [ [0, 2, 2], [0, 1, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'up')
assert result == ['', 0, 0, 2]
# Invalid left jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'left')
assert result == ['', 0, 0, 2]
# Invalid right jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'right')
assert result == ['', 0, 0, 2]
# Illegal right jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'down')
assert result == ['down', 0, 1, 2]
# Moving down from the node
test_branch_iter = [ [0, 2, 2], [0, 3, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], '')
assert result == ['', 0, 4, 2]
# If down element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 3, 2], [0, 4, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], '')
assert result == ['', 0, 3, 2]
# Executing legal down jump to move left
test_branch_iter = [ [0, 2, 2], [0, 3, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'down')
assert result == ['', 0, 4, 2]
# Invalid left jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'left')
assert result == ['', 0, 4, 2]
# Invalid right jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'right')
assert result == ['', 0, 4, 2]
# Illegal up jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'up')
assert result == ['up', 0, 3, 2]
print()
return
class TestNodesBranches(TestCase):
"""
This class tests functions related to processing nodes and
branches of a circuit.
"""
def test_node_checking(self):
"""
node_checking function determines if an element is a node by it's neighbors.
params - circuit matrix
params - sheet, row, column of the element.
params - number of rows and columns of that sheet.
result/modifies - list of nodes to which the element is added if it is a node.
"""
from simulations.network_reader import node_checking
print()
print('*'*80)
print('Testing the node_checking function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_list = []
no_rows = len(test_matrix[0])
no_cols = len(test_matrix[0][0])
no_of_nodes = 0
# The center wire is a legal node
node_checking(test_matrix, test_list, 0, 2, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes != len(test_list))
self.assertTrue(test_list[-1] == [0, 2, 2])
no_of_nodes = len(test_list)
# The element below that is not a node
node_checking(test_matrix, test_list, 0, 1, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# The element in the first row and second column is not a node
node_checking(test_matrix, test_list, 0, 0, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Adding a dummy wire in element in first row, second column
test_matrix[0][0][1] = 'wire'
# STill not a node
node_checking(test_matrix, test_list, 0, 0, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Adding another dummy element to right of it.
test_matrix[0][0][3] = 'wire'
# Now is a node
node_checking(test_matrix, test_list, 0, 0, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes != len(test_list))
self.assertTrue(test_list[-1] == [0, 0, 2])
no_of_nodes = len(test_list)
# Corner element should never be a node
node_checking(test_matrix, test_list, 0, 0, 0, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Resetting the element in first row, second column
test_matrix[0][0][1] = ''
# Element in second row, second column should not be a node
node_checking(test_matrix, test_list, 0, 1, 1, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Placing a component in the element
test_matrix[0][0][1] = 'wire'
# Now the element in second row, second column is a node
node_checking(test_matrix, test_list, 0, 1, 1, no_rows, no_cols)
self.assertTrue(no_of_nodes != len(test_list))
self.assertTrue(test_list[-1] == [0, 1, 1])
no_of_nodes = len(test_list)
# Confirming that a corner element should never be a node
test_matrix[0][0][1] = 'wire'
test_matrix[0][1][0] = 'wire'
node_checking(test_matrix, test_list, 0, 0, 0, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Element in first row, third column should not a node
node_checking(test_matrix, test_list, 0, 2, 0, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Adding a component to the right of it.
test_matrix[0][3][0] = 'wire'
# Now is a node
node_checking(test_matrix, test_list, 0, 2, 0, no_rows, no_cols)
self.assertTrue(no_of_nodes != len(test_list))
self.assertTrue(test_list[-1] == [0, 2, 0])
no_of_nodes = len(test_list)
# Element in 4th row and 2nd column is a node
# This element is null. This method does not check for component.
# The parent method performs the check before calling this function.
node_checking(test_matrix, test_list, 0, 3, 1, no_rows, no_cols)
self.assertTrue(no_of_nodes != len(test_list))
self.assertTrue(test_list[-1] == [0, 3, 1])
no_of_nodes = len(test_list)
# Element in 4th row and 3rd column is not a node.
node_checking(test_matrix, test_list, 0, 3, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Element in last row, 3rd column is not a node
node_checking(test_matrix, test_list, 0, 5, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Adding an element to the left
test_matrix[0][5][1] = 'wire'
# Still not a node
node_checking(test_matrix, test_list, 0, 5, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Adding an element to the right
test_matrix[0][5][3] = 'wire'
# Now a node
node_checking(test_matrix, test_list, 0, 5, 2, no_rows, no_cols)
self.assertTrue(no_of_nodes != len(test_list))
self.assertTrue(test_list[-1] == [0, 5, 2])
no_of_nodes = len(test_list)
# element in last column, 3rd row is not a node
node_checking(test_matrix, test_list, 0, 2, 4, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Setting element below it to be a component
test_matrix[0][1][4] = 'wire'
# Still not a node
node_checking(test_matrix, test_list, 0, 2, 4, no_rows, no_cols)
self.assertTrue(no_of_nodes == len(test_list))
# Setting element above it to be a component
test_matrix[0][3][4] = 'wire'
# Now a node
node_checking(test_matrix, test_list, 0, 2, 4, no_rows, no_cols)
self.assertTrue(no_of_nodes != len(test_list))
self.assertTrue(test_list[-1] == [0, 2, 4])
no_of_nodes = len(test_list)
print()
return
def test_jump_move(self):
"""
jump_move - function to return the next cell after encountering a jump
params - circuit matrix
params - dictionary of jumps with pairs of jumps, each jump - [[sheet, row, col], direction]
params - sheet, row, column of originating jump
params - position of destination jump in jump dictionary value list with the jump label as key.
results - test_matrix, temp_jump_matrix, [0, 2, 2], 1
"""
from simulations.network_reader import jump_move
print()
print('*'*80)
print('Testing the jump_move function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
temp_jump_matrix = {
'jump1': [[[0, 2, 4], 'left'], [[0, 2, 1], 'left']]
}
# First is a jump label that exists. Second is fictitious
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
self.assertTrue(result == ['left', 0, 2, 0])
# Start jump is ok.
# Destination is not a jump. No exception raises. Next cell is an element.
self.assertRaises(Exception, jump_move, test_matrix, temp_jump_matrix, [0, 2, 2], 1)
# Also ok.
temp_jump_matrix['jump1'][1][1] = 'right'
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
self.assertTrue(result == ['right', 0, 2, 2])
# Destination element is not a component.
temp_jump_matrix['jump1'][1][1] = 'up'
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
self.assertTrue(result == ['up', 0, 1, 1])
# Destination element is not a component.
temp_jump_matrix['jump1'][1][1] = 'down'
result = jump_move(test_matrix, temp_jump_matrix, [0, 2, 4], 1)
self.assertTrue(result == ['down', 0, 3, 1])
print()
return
def test_branch_jump(self):
"""
branch_jump takes an element, checks if it is a jump and
returns the element next to the corresponding jump if so
or else return back the element.
params - circuit matrix
params - jump matrix dictionary
params - current element
returns - list [direction, sheet, row, column]
"""
from simulations.network_reader import branch_jump
print()
print('*'*80)
print('Testing the branch_jump function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
# First is a jump label that exists. Second is fictitious
temp_jump_matrix = {
'jump1': [[[0, 2, 4], 'left'], [[0, 2, 1], 'left']]
}
# If the element is not a jump, jump executed is null (not executed)
# and same element is returned.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 3])
self.assertTrue(result == ['', 0, 2, 3])
# If element is jump, element next to corresponding jump is returned.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['left', 0, 2, 0])
# Also ok.
temp_jump_matrix['jump1'][1][1] = 'right'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['right', 0, 2, 2])
# Element does not exist. Error checking does not happen at this stage.
temp_jump_matrix['jump1'][1][1] = 'up'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['up', 0, 1, 1])
# Element does not exist. Error checking does not happen at this stage.
temp_jump_matrix['jump1'][1][1] = 'down'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['down', 0, 3, 1])
# Flipping the jump labels to check if method still maps jump labels.
temp_jump_matrix = {
'jump1': [ [[0, 2, 1], 'left'], [[0, 2, 4], 'left'] ]
}
# No jump, same behaviour.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 3])
self.assertTrue(result == ['', 0, 2, 3])
# Again no jump.
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 1])
self.assertTrue(result == ['', 0, 2, 1])
# Legal jump
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['left', 0, 2, 0])
# Legal jump
temp_jump_matrix['jump1'][0][1] = 'right'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['right', 0, 2, 2])
# Element does not exist, no error.
temp_jump_matrix['jump1'][0][1] = 'up'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['up', 0, 1, 1])
# Element does not exist, no error.
temp_jump_matrix['jump1'][0][1] = 'down'
result = branch_jump(test_matrix, temp_jump_matrix, [0, 2, 4])
self.assertTrue(result == ['down', 0, 3, 1])
print()
return
def test_branch_advance(self):
"""
branch_advance takes an element and returns the next element
to extend the branch checking whether the element already is
in the temporary branch or if a jump is executed.
params - circuit matrix
params - temporary branch
params - current element
params - jump executed
returns - [jump, next element]
"""
from simulations.network_reader import branch_advance
print()
print('*'*80)
print('Testing the branch_advance function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_branch_iter = [ [0, 2, 2] ]
# Since starting with a node that has elements on all 4 sides,
# First check is for an element to the left if no jump is specified.
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
self.assertTrue(result == ['', 0, 2, 1])
# Since left element is in temp branch, next check is to the top
test_branch_iter = [ [0, 2, 1], [0, 2, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
self.assertTrue(result == ['', 0, 1, 2])
# Since top element is in temp branch, next check is to the right
test_branch_iter = [ [0, 2, 1], [0, 2, 2], [0, 1, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
self.assertTrue(result == ['', 0, 2, 3])
# Since right element is in temp branch, next check is to the bottom
test_branch_iter = [ [0, 2, 1], [0, 2, 2], [0, 1, 2], [0, 2, 3] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
self.assertTrue(result == ['', 0, 3, 2])
# Since all adj elements are in temp branch, fails returning back node.
test_branch_iter = [ [0, 2, 1], [0, 2, 2], [0, 1, 2], [0, 2, 3], [0, 3, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 2], '')
self.assertTrue(result == ['', 0, 2, 2])
# Extending branch to the right from node.
test_branch_iter = [ [0, 2, 2], [0, 2, 3] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], '')
self.assertTrue(result == ['', 0, 2, 4])
# If right element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 2, 3], [0, 2, 4] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], '')
self.assertTrue(result == ['', 0, 2, 3])
# Executing legal right jump to move right
test_branch_iter = [ [0, 2, 2], [0, 2, 3] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'right')
self.assertTrue(result == ['', 0, 2, 4])
# Invalid down jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'down')
self.assertTrue(result == ['', 0, 2, 4])
# Invalid up jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'up')
self.assertTrue(result == ['', 0, 2, 4])
# Illegal left jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 3], 'left')
self.assertTrue(result == ['left', 0, 2, 3])
# Moving left from the node
test_branch_iter = [ [0, 2, 2], [0, 2, 1] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], '')
self.assertTrue(result == ['', 0, 2, 0])
# If left element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 2, 1], [0, 2, 0] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], '')
self.assertTrue(result == ['', 0, 2, 1])
# Executing legal left jump to move left
test_branch_iter = [ [0, 2, 2], [0, 2, 1] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'left')
self.assertTrue(result == ['', 0, 2, 0])
# Invalid down jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'down')
self.assertTrue(result == ['', 0, 2, 0])
# Invalid up jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'up')
self.assertTrue(result == ['', 0, 2, 0])
# Illegal right jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 2, 1], 'right')
self.assertTrue(result == ['right', 0, 2, 1])
# Moving up from the node
test_branch_iter = [ [0, 2, 2], [0, 1, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], '')
self.assertTrue(result == ['', 0, 0, 2])
# If up element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 1, 2], [0, 0, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], '')
self.assertTrue(result == ['', 0, 1, 2])
# Executing legal up jump to move left
test_branch_iter = [ [0, 2, 2], [0, 1, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'up')
self.assertTrue(result == ['', 0, 0, 2])
# Invalid left jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'left')
self.assertTrue(result == ['', 0, 0, 2])
# Invalid right jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'right')
self.assertTrue(result == ['', 0, 0, 2])
# Illegal right jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 1, 2], 'down')
self.assertTrue(result == ['down', 0, 1, 2])
# Moving down from the node
test_branch_iter = [ [0, 2, 2], [0, 3, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], '')
self.assertTrue(result == ['', 0, 4, 2])
# If down element is included, advance fails returning current element
test_branch_iter = [ [0, 2, 2], [0, 3, 2], [0, 4, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], '')
self.assertTrue(result == ['', 0, 3, 2])
# Executing legal down jump to move left
test_branch_iter = [ [0, 2, 2], [0, 3, 2] ]
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'down')
self.assertTrue(result == ['', 0, 4, 2])
# Invalid left jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'left')
self.assertTrue(result == ['', 0, 4, 2])
# Invalid right jump but function checks only for adjacent element
# that is not in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'right')
self.assertTrue(result == ['', 0, 4, 2])
# Illegal up jump as left element is in temp branch
result = branch_advance(test_matrix, test_branch_iter, [0, 3, 2], 'up')
self.assertTrue(result == ['up', 0, 3, 2])
print()
return


The tests work ok except for a few discrepancies. There is a case in the branch_advance method where an invalid jump does not throw an exception or even return an indication of an error, but rather returns the correct element. For example, while advancing to the right on a branch, the method checks if there is an element to the right of the current element and if that element has already been added to the temporary branch. Legally the advance should occur if the element on the right exists. There could be no jump executed in which case it is simply an element that exists to the right of the last element in the temporary branch. If a jump is executed, the jump direction should be to the right.

However, if the jump executed has a direction of up, down or right, there will be no exception and the element to the right of the current element will be returned. The only time an exception will be generated will be when the jump direction executed will be to the left. The reason this did not cause a major issue was because the methods related to the jump are fairly ok and do not produce wrong jump directions. But in principle, the code is wrong.

The check should be, according to the jump direction, is there an element in that direction? If there is no jump, we could follow some sequence of directions while searching for the next element.

Now that the methods have been tested, the next issue is to test the outer method determine_nodes_branches which calls these methods. At this point, the only was to test this outer method is manually by inputting different circuits. And that doesn't seem like a good way to test as the testing is not at all automatic and how many circuits can I come up with?

This brings me to the next crossroad. I need an automatic circuit builder. This is because things will get more complicated at the next step where I will have to test loops and loop manipulations. I need to generate loops automatically while also introducing errors and bugs in the loops automatically. So, the circuit builder will need to generate circuit topologies only while testing at every stage. For example, I need to start with a simple two node, three branch, two loop circuit and verify that that is generated. Then continuously add nodes and branches automatically to form new loops. The structure should not matter as long as there are a few random jump labels inserted every now and then.

This circuit builder seems very interesting and challenging. The next few blog posts will be dedicated to this.

Thursday, December 12, 2019

Testing jump labels

I finally wrote the first bunch of serious tests with respect to the way the simulator processes circuits. I grouped together a few tests that test the jump labels in a circuit under a class. The code can be found in my bitbucket and sourceforge repos:
https://bitbucket.org/shivkiyer/ppe_simulator/src/testing/
https://sourceforge.net/p/pythonpowerelec/code/ci/testing/tree/

Also, to get a full length course on power electronics simulations, check out my online course:
https://www.udemy.com/course/simulating-power-electronic-circuits-using-python/

The class in the command line interface is:

class TestJumpHandling:
"""
This class groups together tests related to checking and handling jump
labels in the circuit schematic.
"""
def test_jump_sanity(self):
"""
jump_sanity checks for whether an element in the complete system matrix
is a jump, a component or no connection.
param - 3 dimensional array (3 level nested list) of elements.
param - the sheet, row, column position.
param - an indicator dictionary about whether the element is a jump, a component or null.
result - a modification to the indicator dictionary object.
"""
from network_reader import jump_sanity, scrub_elements
print()
print('*'*80)
print('Testing whether a jump can be identitifed on a circuit schematic element')
test_matrix = [
[
['wire', '', 'dyxcsdc', 'jump1', 'Resistor'],
[' ', '', ' jump2', ' \n ', ' wire '],
],
[
['wire', '', 'dyxcsdc', 'jump1', 'Resistor'],
[' ', '', ' jump2', ' \n ', ' wire '],
],
]
def repeat_test(sheet, row, column, expected_val):
"""
A quick test method that takes the co-ordinates and
checks the expected value.
"""
test_element = {"exist":0, "jump":1}
jump_sanity(test_matrix, test_element, sheet, row, column)
assert test_element == expected_val
return
repeat_test(0, 0, 0, {"exist":0})
repeat_test(0, 0, 1, {})
repeat_test(0, 0, 2, {"exist":0})
repeat_test(0, 0, 3, {"jump":1})
repeat_test(0, 0, 4, {"exist":0})
# empty space cannot be handled and is treated as an element
repeat_test(0, 1, 0, {"exist":0})
repeat_test(0, 1, 1, {})
# trailing empty space turns jump into an element
repeat_test(0, 1, 2, {"exist":0})
# newline character is seen as an element
repeat_test(0, 1, 3, {"exist":0})
repeat_test(0, 1, 4, {"exist":0})
# Repitition to check for multiple sheets.
repeat_test(1, 0, 0, {"exist":0})
repeat_test(1, 0, 1, {})
repeat_test(1, 0, 2, {"exist":0})
repeat_test(1, 0, 3, {"jump":1})
repeat_test(1, 0, 4, {"exist":0})
repeat_test(1, 1, 0, {"exist":0})
repeat_test(1, 1, 1, {})
repeat_test(1, 1, 2, {"exist":0})
repeat_test(1, 1, 3, {"exist":0})
repeat_test(1, 1, 4, {"exist":0})
print()
return
def test_jump_checking(self):
"""
jump_checking - checks if a jump label is legal
jumps have to be extreme elements in a branch
jumps cannot be next to each other
params - complete circuit matrix, and sheet, row, col co-ordinates
params - rows, columns in that particular sheet
params - list of circuit spreadsheet names
returns/modifies - jump list adding a legal jump to it
"""
from network_reader import jump_checking
print()
print('*'*80)
print('Testing the jump_checking function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_jump = []
test_nw_name = ['test1',]
test_no_of_rows = len(test_matrix[0])
test_no_of_cols = len(test_matrix[0][0])
# jump1 is a legal jump
jump_checking(test_matrix, test_jump, 0, 2, 4, test_no_of_rows, test_no_of_cols, test_nw_name)
assert len(test_jump) == 1
assert test_jump[0] == [0, 2, 4, 'jump1', 'left']
# jump2 is at a node. Exception is not specially stating that it cannot be a node.
test_matrix[0][2][2] = 'jump2'
with pytest.raises(SystemExit):
jump_checking(test_matrix, test_jump, 0, 2, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
test_matrix[0][2][2] = 'wire'
# A legal jump at the extreme bottom
test_matrix[0][5][2] = 'jump2'
jump_checking(test_matrix, test_jump, 0, 5, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
assert len(test_jump) == 2
assert test_jump[1] == [0, 5, 2, 'jump2', 'up']
# Illegal - jump not extreme element on branch.
test_matrix[0][4][2] = 'jump3'
with pytest.raises(SystemExit):
jump_checking(test_matrix, test_jump, 0, 4, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
test_matrix[0][4][2] = 'wire'
# jump2 is not extreme in a branch. Exception is not specially stating that it cannot be next to a node.
test_matrix[0][1][2] = 'jump3'
with pytest.raises(SystemExit):
jump_checking(test_matrix, test_jump, 0, 1, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
test_matrix[0][0][2] = ''
# This should raise an exception because a jump should not be next to a node.
# But this test fails
# with pytest.raises(SystemExit):
# jump_checking(test_matrix, test_jump, 0, 1, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
# Illegal - two jumps next to each other
test_matrix[0][0][2] = 'jump3'
with pytest.raises(SystemExit):
jump_checking(test_matrix, test_jump, 0, 0, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
# Resetting the element below makes it a legal jump
test_matrix[0][1][2] = 'xyz'
jump_checking(test_matrix, test_jump, 0, 0, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
assert len(test_jump) == 3
assert test_jump[2] == [0, 0, 2, 'jump3', 'down']
# Illegal - not extreme element in a branch
test_matrix[0][2][1] = 'jump4'
with pytest.raises(SystemExit):
jump_checking(test_matrix, test_jump, 0, 2, 1, test_no_of_rows, test_no_of_cols, test_nw_name)
# Illegal - jumps next to each other
test_matrix[0][2][0] = 'jump5'
with pytest.raises(SystemExit):
jump_checking(test_matrix, test_jump, 0, 2, 0, test_no_of_rows, test_no_of_cols, test_nw_name)
# Restting the jump to the right to an element makes it a legal jump
test_matrix[0][2][1] = 'xyz'
jump_checking(test_matrix, test_jump, 0, 2, 0, test_no_of_rows, test_no_of_cols, test_nw_name)
assert len(test_jump) == 4
assert test_jump[3] == [0, 2, 0, 'jump5', 'right']
# Illegal - jump labels next to each other
test_matrix[0][0][1] = 'jump6'
with pytest.raises(SystemExit):
jump_checking(test_matrix, test_jump, 0, 0, 1, test_no_of_rows, test_no_of_cols, test_nw_name)
# Legal jump when element to the right is reset to a component.
test_matrix[0][0][2] = 'compo'
jump_checking(test_matrix, test_jump, 0, 0, 1, test_no_of_rows, test_no_of_cols, test_nw_name)
assert len(test_jump) == 5
assert test_jump[4] == [0, 0, 1, 'jump6', 'right']
return
def test_jump_node_check(self):
"""
jump_node_check - checks whether a jump label is next to a node.
param - entire circuit matrix
param - list of nodes
param - a direction in which a particular node has an element
param - index of that particular node
param - list of circuit spreadsheet names
result - throws an error if in the direction specified for the node,
there is a jump label in the adjacent cell. Adjacent cell is measured
with respect to the node.
"""
from network_reader import jump_node_check
print()
print('*'*80)
print('Testing the jump_node_check function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_node_list = [
[0, 2, 2]
]
test_nw_name = ['test1',]
try:
jump_node_check(test_matrix, test_node_list, 'left', 0, test_nw_name)
except:
pytest.fail('This should haved worked')
test_matrix[0][2][1] = 'jump2'
with pytest.raises(SystemExit):
jump_node_check(test_matrix, test_node_list, 'left', 0, test_nw_name)
test_matrix[0][2][1] = 'xyz'
test_matrix[0][2][0] = 'jump2'
try:
jump_node_check(test_matrix, test_node_list, 'left', 0, test_nw_name)
except:
pytest.fail('This should haved worked')
test_matrix[0][1][2] = 'jump3'
with pytest.raises(SystemExit):
jump_node_check(test_matrix, test_node_list, 'up', 0, test_nw_name)
test_matrix[0][1][2] = 'xyz'
test_matrix[0][0][2] = 'jump3'
try:
jump_node_check(test_matrix, test_node_list, 'up', 0, test_nw_name)
except:
pytest.fail('This should haved worked')
test_matrix[0][3][2] = 'jump4'
with pytest.raises(SystemExit):
jump_node_check(test_matrix, test_node_list, 'down', 0, test_nw_name)
test_matrix[0][3][2] = 'xyz'
test_matrix[0][4][2] = 'jump4'
try:
jump_node_check(test_matrix, test_node_list, 'down', 0, test_nw_name)
except:
pytest.fail('This should haved worked')
print()
return
view raw jumptest.py hosted with ❤ by GitHub


The functionalities that I am testing for specifically are:
  1. Check whether an element is a component (could also be a wire), a jump label or no connection at all.
  2. A jump must be the extreme element in a branch segment. If it has components on more than one side, that is a violation that throws an exception.
  3. Two jump labels cannot be adjacent to each other - that is a violation that throws an exception.
  4. A jump label cannot be next to a node - a violation that throws an exception.

Most of the tests could be accomplished by a simple assert statement. However, to check if a method throws an exception, I found this block to be most useful:

with pytest.raises(SystemExit):
    ...method...

So, the method or the block of code must throw an error or else the test fails. SystemExit could be replaced by other errors, but in this case, all I do is exit with an error code of 1 and so a SystemExit is all I can check for.

Conversely, if a method or block of code should not throw and error, the test can be written as:

try:
    ...method...
except:
    pytest.fail('This should haved worked')


The class for the web app is a bit different:

class TestJumpHandling(TestCase):
"""
This class groups together tests related to checking and handling jump
labels in the circuit schematic.
"""
def test_jump_sanity(self):
"""
jump_sanity checks for whether an element in the complete system matrix
is a jump, a component or no connection.
param - 3 dimensional array (3 level nested list) of elements.
param - the sheet, row, column position.
param - an indicator dictionary about whether the element is a jump, a component or null.
result - a modification to the indicator dictionary object.
"""
from simulations.network_reader import jump_sanity, scrub_elements
print()
print('*'*80)
print('Testing whether a jump can be identitifed on a circuit schematic element')
test_matrix = [
[
['wire', '', 'dyxcsdc', 'jump1', 'Resistor'],
[' ', '', ' jump2', ' \n ', ' wire '],
],
[
['wire', '', 'dyxcsdc', 'jump1', 'Resistor'],
[' ', '', ' jump2', ' \n ', ' wire '],
],
]
def repeat_test(sheet, row, column, expected_val):
"""
A quick test method that takes the co-ordinates and
checks the expected value.
"""
test_element = {"exist":0, "jump":1}
jump_sanity(test_matrix, test_element, sheet, row, column)
self.assertEqual(test_element, expected_val)
return
repeat_test(0, 0, 0, {"exist":0})
repeat_test(0, 0, 1, {})
repeat_test(0, 0, 2, {"exist":0})
repeat_test(0, 0, 3, {"jump":1})
repeat_test(0, 0, 4, {"exist":0})
# empty space cannot be handled and is treated as an element
repeat_test(0, 1, 0, {"exist":0})
repeat_test(0, 1, 1, {})
# trailing empty space turns jump into an element
repeat_test(0, 1, 2, {"exist":0})
# newline character is seen as an element
repeat_test(0, 1, 3, {"exist":0})
repeat_test(0, 1, 4, {"exist":0})
# Repitition to check for multiple sheets.
repeat_test(1, 0, 0, {"exist":0})
repeat_test(1, 0, 1, {})
repeat_test(1, 0, 2, {"exist":0})
repeat_test(1, 0, 3, {"jump":1})
repeat_test(1, 0, 4, {"exist":0})
repeat_test(1, 1, 0, {"exist":0})
repeat_test(1, 1, 1, {})
repeat_test(1, 1, 2, {"exist":0})
repeat_test(1, 1, 3, {"exist":0})
repeat_test(1, 1, 4, {"exist":0})
print()
return
def test_jump_checking(self):
"""
jump_checking - checks if a jump label is legal
jumps have to be extreme elements in a branch
jumps cannot be next to each other
params - complete circuit matrix, and sheet, row, col co-ordinates
params - rows, columns in that particular sheet
params - list of circuit spreadsheet names
returns/modifies - jump list adding a legal jump to it
"""
from simulations.network_reader import jump_checking
import simulations.circuit_exceptions as CktEx
print()
print('*'*80)
print('Testing the jump_checking function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_jump = []
test_nw_name = ['test1',]
test_no_of_rows = len(test_matrix[0])
test_no_of_cols = len(test_matrix[0][0])
# jump1 is a legal jump
jump_checking(test_matrix, test_jump, 0, 2, 4, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertEqual(test_jump[-1], [0, 2, 4, 'jump1', 'left'])
# jump2 is at a node. Exception is not specially stating that it cannot be a node.
test_matrix[0][2][2] = 'jump2'
result = jump_checking(test_matrix, test_jump, 0, 2, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertTrue(len(result) > 0)
test_matrix[0][2][2] = 'wire'
# A legal jump at the extreme bottom
test_matrix[0][5][2] = 'jump2'
jump_checking(test_matrix, test_jump, 0, 5, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertEqual(test_jump[-1], [0, 5, 2, 'jump2', 'up'])
# Illegal - jump not extreme element on branch.
test_matrix[0][4][2] = 'jump3'
result = jump_checking(test_matrix, test_jump, 0, 4, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertTrue(len(result) > 0)
test_matrix[0][4][2] = 'wire'
# jump2 is not extreme in a branch. Exception is not specially stating that it cannot be next to a node.
test_matrix[0][1][2] = 'jump3'
result = jump_checking(test_matrix, test_jump, 0, 1, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertTrue(len(result) > 0)
test_matrix[0][0][2] = ''
# This should raise an exception because a jump should not be next to a node.
# But this test fails
# result = jump_checking(test_matrix, test_jump, 0, 1, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
# self.assertTrue(len(result) > 0)
# Illegal - two jumps next to each other
test_matrix[0][0][2] = 'jump3'
result = jump_checking(test_matrix, test_jump, 0, 0, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertTrue(len(result) > 0)
# Resetting the element below makes it a legal jump
test_matrix[0][1][2] = 'xyz'
jump_checking(test_matrix, test_jump, 0, 0, 2, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertEqual(test_jump[-1], [0, 0, 2, 'jump3', 'down'])
# Illegal - not extreme element in a branch
test_matrix[0][2][1] = 'jump4'
result = jump_checking(test_matrix, test_jump, 0, 2, 1, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertTrue(len(result) > 0)
# Illegal - jumps next to each other
test_matrix[0][2][0] = 'jump5'
result = jump_checking(test_matrix, test_jump, 0, 2, 0, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertTrue(len(result) > 0)
# Restting the jump to the right to an element makes it a legal jump
test_matrix[0][2][1] = 'xyz'
jump_checking(test_matrix, test_jump, 0, 2, 0, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertEqual(test_jump[-1], [0, 2, 0, 'jump5', 'right'])
# Illegal - jump labels next to each other
test_matrix[0][0][1] = 'jump6'
result = jump_checking(test_matrix, test_jump, 0, 0, 1, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertTrue(len(result) > 0)
# Legal jump when element to the right is reset to a component.
test_matrix[0][0][2] = 'compo'
jump_checking(test_matrix, test_jump, 0, 0, 1, test_no_of_rows, test_no_of_cols, test_nw_name)
self.assertEqual(test_jump[-1], [0, 0, 1, 'jump6', 'right'])
return
def test_jump_node_check(self):
"""
jump_node_check - checks whether a jump label is next to a node.
param - entire circuit matrix
param - list of nodes
param - a direction in which a particular node has an element
param - index of that particular node
param - list of circuit spreadsheet names
result - throws an error if in the direction specified for the node,
there is a jump label in the adjacent cell. Adjacent cell is measured
with respect to the node.
"""
from simulations.network_reader import jump_node_check
print()
print('*'*80)
print('Testing the jump_node_check function')
print()
test_matrix = [
[
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['dyxcsdc', 'wire', 'wire', 'Resistor', 'jump1'],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
['', '', 'wire', '', ''],
],
]
test_node_list = [
[0, 2, 2]
]
test_nw_name = ['test1',]
result = jump_node_check(test_matrix, test_node_list, 'left', 0, test_nw_name)
self.assertTrue(len(result) == 0)
test_matrix[0][2][1] = 'jump2'
result = jump_node_check(test_matrix, test_node_list, 'left', 0, test_nw_name)
self.assertTrue(len(result) > 0)
test_matrix[0][2][1] = 'xyz'
test_matrix[0][2][0] = 'jump2'
result = jump_node_check(test_matrix, test_node_list, 'left', 0, test_nw_name)
self.assertTrue(len(result) == 0)
test_matrix[0][1][2] = 'jump3'
result = jump_node_check(test_matrix, test_node_list, 'up', 0, test_nw_name)
self.assertTrue(len(result) > 0)
test_matrix[0][1][2] = 'xyz'
test_matrix[0][0][2] = 'jump3'
result = jump_node_check(test_matrix, test_node_list, 'up', 0, test_nw_name)
self.assertTrue(len(result) == 0)
test_matrix[0][3][2] = 'jump4'
result = jump_node_check(test_matrix, test_node_list, 'down', 0, test_nw_name)
self.assertTrue(len(result) > 0)
test_matrix[0][3][2] = 'xyz'
test_matrix[0][4][2] = 'jump4'
result = jump_node_check(test_matrix, test_node_list, 'down', 0, test_nw_name)
self.assertTrue(len(result) == 0)
print()
return


The primary reason is that the web application does not exit with an error code when a violation takes place but merely displays the errors to the user on the web browser. So the check is for the result of the methods to be a non null list that would be an error message.

One major advantage of testing was that I found myself investigating code and thinking of possible ways that it could fail. It is a completely different form of coding as opposed to regular development where you are just trying to get the code to work. In testing, you are thinking of ways to break the code. This is a totally different aspect to development that can be fun if you look at it the right way.