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:



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:



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.













Thursday, December 5, 2019

Tested basic spreadsheet operations


The first basic group of tests have been completed. These are tests related to reading circuit schematics and manipulating the elements of the spreadsheet. The latest code can be found in the testing branch in the repositories:


https://bitbucket.org/shivkiyer/ppe_simulator/src/testing/

https://sourceforge.net/p/pythonpowerelec/code/ci/testing/tree/


Most tests gave expected results. The major TODO in this area would be to make the spreadsheets more flexible. Currently, only CSV files can be read and the delimiter has to be a comma. For anyone who is not a hardcore developer, this might be an issue as very few people deal with CSV files on a regular basis. Spreadsheet files are usually .xlsx files or variants depending on the version of Office.

For now, I will continue on testing other parts of the simulator before making logical changes to the code.

Sunday, October 27, 2019

Unit testing of circuit schematic operations - Part I

After setting up the test environments for the command line and the web app in the previous post, I now get started with some serious testing. To begin with, the code for this can be found in the testing branch in either of these two repositories:
https://sourceforge.net/p/pythonpowerelec/code/ci/testing/tree/
https://bitbucket.org/shivkiyer/ppe_simulator/branch/testing

I expanded the testing of csv_element_2D method to the testing of csv_element_2D and csv_tuple_2D as these two methods quite often go hand in hand since one converts a tuple to a string cell position and the vice versa. Here is the code:

There are two types of tests. First, the manual tests are tests where I know there could be a problem and these are the borderline cases - when for example 1Z becomes 1AA or 9A becomes 10A etc. Manual tests can be limited unless you have a lot of time or the function is very complicated and only manual tests are possible. In this case, automatic tests are also possible. For automatic tests, I randomly generate tuples of x (row) and y (column) positions and convert them into string cell positions using csv_element_2D and then convert the resultant cell positions back into tuples using csv_tuple_2D. I chose 20 such iterations and made asserts for each case.

The method can be extended to the Django web app with minor modifications - only the assert statement of pytest will be replaced by assertEqual of TestCase.

Next comes the csv_reader method that takes in a file object which is the .csv file and returns a 2D matrix (lists embedded within a list) representation of the circuit. This is the code:

It is important to write the desired circuit contents to a file. Simply passing the string contents to csv_reader as the argument will not work. This is because when Python opens a file, it creates an iterable object and so you can iterate line by line. However, if I pass a string, the iteration will happen character by character which is not what we want.

I didn't bother with circuit components or circuit topology because what matters here is the reading of the .csv file. A few errors are expected:
  1. If the separator is anything other than a comma, the reading fails.
  2. If one line has an extra character, the reading fails. This is because the expected result is a 2D matrix. This might be ok in most cases, but it might be a good idea to put a catch block in case, some weird spreadsheet software passes through a schematic with unequal line lengths. This should result in a user readable error rather than a syntax error.
  3. The elements are scrubbed internally which means leading and trailing spaces are removed from the elements. This is ok.

Will need to spend some thought on this as spreadsheet software in general are a bit messy and the need to generate .csv files in a particular form may not be obvious to the normal user.

Sunday, October 20, 2019

Starting unit testing in Python Power Electronics

My work as a web developer has introduced me to the wonders of unit testing. No one likes writing tests and neither did I for a very long time. I relied on manual testing like many others. Until I started writing unit tests in Jasmine for my web apps. The number of scenarios that you can test makes your code way more reliable.

Another advantage of testing is that quite often there are changes you would like to make but don't want to make them right now as you might break the code that is working. And in the end these changes never happen as you get too busy with other stuff. Here, test driven development can solve the problem of this inertia. Even if you do not want to change the code itself, test suites can help to totally determine every potential lapse in a block of code that will take away the fear bit by bit of finally getting around to fixing them.

With these noble thoughts, I have finally got over my laziness and decided to write tests for the circuit simulator. As a start, Python comes with the in-built unittest module for writing tests. Django uses this and creates a class TestCase to test API end points. Besides this, Python also has the pytest module that needs to be separately installed.

So, I decided to write unit tests for the command line interface with pytest and for the web app using Django's TestCase class based on unittest. In the beginning, the code for these will be separate. However, with time, the plan will be have one set of tests and also at the same time have one set of code for both web apps.

So, the directory structure for the simulator is:

README
command_line
            --  circuit_solver.py
            --  circuit_elements.py
            --  circuit_exceptions.py
            --  LICENSE.txt
            --  matrix.py
            --  network_reader.py
            --  solver.py

web_app
            -- requirements.txt
            -- simulator_interface
                     -- LICENSE.txt
                     -- manage.py
                     -- simulation_collection
                     -- media_files
                     -- simulator_interface
                                 -- settings.py
                                 -- urls.py
                                 -- wsgi.py
                     -- simulations
                                 -- circuit_solver.py
                                 -- circuit_elements.py
                                 -- circuit_exceptions.py
                                 -- solver.py
                                 -- network_reader.py
                                 -- matrix.py
                                 -- admin.py
                                 -- views.py
                                 -- models.py
                                 -- tests.py


There may be a few more directories and files, but these are the major ones at least for testing. We will create a tests directory inside command_line directory and house the command line app tests there. We will remove the tests.py file inside the simulations directory  inside web_app/simulator_interface and create a tests directory for the web app tests.

To install pytest, just do:
pip install pytest

For the command line app, I decided to get started with testing by writing a very simple test for the function csv_element_2D:



This function takes a tuple/list that represents a row, column position and converts it to a string that represents the cell position. So, [0,0] will be "1A" on a spreadsheet. So, a test for this function in the the command_line app would be:



The test itself if very simple. We import the method from the file network_reader.py that has the method. The test function has to start with test_ for pytest to execute it. The asset method in pytest merely asserts that for the test to pass, the value returned by the function for a particular argument has to be equal to a certain value.

The challenge in getting this test to run was in pytest being able to find the file/module network_reader.py. For this the first block of code above the function had to be written. I had to extract the parent method and insert it into the python path so that Python will look in that directory for the module and import it.

The test for the web app was a bit easier as I didn't have to struggle with path as much. The only challenge was I was trying to use pytest and it would not work as pytest for some reason could not find django module that is imported in the web app modules. The reason I can think of is in Django 2 onwards, the app needs to be loaded and therefore, django is not available until the app is working. For this, I had to go with the default Django TestCase class.



To run these tests. Inside command_line directory, just run the command:
pytest

There are several arguments that can be passed and I will investigate as time goes on. For the web app, inside the directory that contains manage.py, run:
python manage.py test simulations

The app name simulations is important because the tests directory with the test is inside the simulations app directory.


Friday, March 15, 2019

Second failed attempt at linear model of an LCL filter

In my previous blog post, I had described how trying to approximate a differential of Ldi/dt with a linear model is probably impossible. However, there was a flaw in the first linear model that I attempted between the filter capacitor voltage, the grid current and the grid voltage. I had chosen a case where the grid current were always in quadrature to the grid voltage and the filter capacitor voltage has the same template as the grid voltage.

So, to check out if the flaw would result in any improvement, I used a training model where the filter capacitor voltages were different from the grid voltages both by magnitude and by phase. The result was an equally flawed linear model.

vcf = -0.2091*igrid_ref + 1.1028*vgrid

As is evident, this model is even worse that the previous one. Because of the phase angle variation, the model has chosen a larger multiplying factor for the grid voltage of 1.1028 but has tried to compensate with a larger resistance of 0.2091 which is useless in the face of the multiplying factor to the grid voltage.

So, the conclusion is that trying to approximate a differential in this manner by a linear model using linear regression is quite useless. So, I need to step back and figure out what the major challenge with controlling the current through the grid inductor of a LCL filter is.

The transfer function between the inverter and the final grid current is a 3rd order polynomial. Physically, the challenge is in accurately regulating the filter capacitor voltage so as to regulate the grid current to a desired value. The simplest control that can be achieved in this case is controlling the current immediately at the output of the inverter. In that cases, the transfer function between the inverter current and the inverter voltage (or modulation index) is a first order polynomial.

The problem is that inverter output current and the grid current will differ by a current that flows through the filter capacitor. Again, the problem would boil to estimating the current flowing through the filter capacitor. So, to avoid any form of estimation either of current or of voltages, how do we achieve a closed loop control of the final grid current without any synchronous transformation?

The reason why I avoid synchronous transformation is that it transforms the grid current to dc values. This assumes that there are no harmonics are intended to be injected in the grid currents because if the converter is a harmonic filter and is expected to inject harmonics into the grid, the synchronous transformation will have to be performed for every harmonic.

So with these objectives laid out, I will find better ways to control the inverter with advanced concepts of machine learning.

Monday, March 11, 2019

Failed first attempt at linear model for inverter with LCL filter

In the previous blog post, I had attempted to create a linear model to predict the reference for the filter capacitor voltage such that the current injected into the grid will track it's reference. And when I saw the linear model, I had the feeling, it would probably not work. And it didn't.

The linear model that the LinearRegression module with scikit.linear_model gave me was:

vcf_ref = -0.01157 + 0.04176*igrid_ref + 1.0587*vgrid

Seems elegant and when I tried to fit it to the test set, I got a cluster that fell along a linear line. All this seemed wonderful as an application of machine learning to power electronics. But looking at it from a power electronics perspective, what did I get?

vcf_ref = vdc + Remu*igrid_ref + K*vgrid

Where vdc is a dc bias that apparently the linear model feels is needed. Though there should not be a dc bias needed in an ac system unless there is a dc component due to some severe unbalanced harmonic load, this dc bias is fairly small and can be neglected.

The Remu is actually what the linear model approximated the inductor as. The actual equation is:

vcf = vgrid + igrid*R + L* digrid/dt

The linear model has approximated (igrid*R + L* digrid/dt) by Remu (emulated resistance). And this Remu = 0.04176 which is very small given the fact that an inductor is being modeled by this resistor.

The next major problem is the 1.0587*vgrid. We are scaling the grid voltage by 5.8%. And compared to the other two factors, this is the major factor. So really, all that this linear model does to try to create a reference filter capacitor voltage is to scale up the grid voltage by 5.8%. And this is totally inadequate.

Suppose, the current injected into the grid will have to be in-phase with the grid voltage and this would be a requirement for many distributed energy resources. The filter capacitor voltage would need a phase advance with respect to the grid voltage. Or else, it would not be able to produce a grid current in phase with the grid voltage. Because the current through the inductor would be in quadrature and lagging behind the voltage drop across it.

With the above linear model, the filter capacitor voltage would be scaled up by 5% and so in phase with the grid voltage. The voltage drop across the grid inductor would be in phase with the grid voltage and therefore, the current injected into the grid would be lagging behind the grid voltage by 90 degrees. Which is not what we want.

My hypothesis is that what I need is a more detailed and accurate model for the differential operator. And that may need more complicated techniques that a LinearRegression. But before jumping to that, I would like to exhaust any possible solution that linear models have just to gain knowledge.

In all fairness, the linear model that I got from LinearRegression above was a flawed one. Because what I did to train the model was simplistic. To begin with, I had chosen the equilibrium point to be a modulation signal that was the grid voltage scaled by the dc bus:

m = vgrid/vdc

So, the filter capacitor voltage is very close to the grid voltage in magnitude and most importantly in phase as well. To this modulation signal, I had added a random disturbance.

m + mrand*sin(wt)

Here was the mistake - I was giving the random modulation signal perturbation the same phase as the modulation signal. And so, the filter capacitor voltage voltage only changes in magnitude.

And this is exactly why the linear model we ended up with has the major component that scales up the grid voltage by 5%. Because the linear model saw that in a vast majority of cases, that's all there was to it - the capacitor voltage scaled up by small amount and the grid current in quadrature with the grid voltage. So, the linear model assumed this was the ultimate truth and gave that very convenient equation.

So, my second attempt would be to train the linear model making sure that the filter capacitor voltage differed from the grid voltage by not just magnitude by also in phase.

So, check out these two current snapshots in the previous training case:




In both cases, the current is always 90 degrees lagging behind the grid voltage because filter capacitor voltage is in-phase with the grid voltage. So, the linear model thinks this is the norm, this is always how it is.

In contrast, now I am going to add two components to the random shuffle to the modulation index - an in-phase component and a quadrature component. So, the grid current will have a varying phase angle difference with the grid voltage. Below is an example. With this, I am going to try and retrain the linear model. Probably still won't work, but let's see how it changes.




Saturday, March 9, 2019

First attempt at a linear model for an LCL filter

In the previous blog post I described how I am trying to use machine learning to create a functional relationship between the grid voltage and the filter capacitor voltage with respect to the grid current for a dc-ac converter connected to an ac grid through an LCL filter.

So, if that is a bit of a mouthful, here is what it is in a flow:

Inverter - Inductor - Filter Capacitor - Inductor - Grid

The idea is can we determine how much the voltage across the filter capacitor  should be for a particular value of current injected into the grid? The grid voltage can be measured. So, we have:

1. Measured value of grid voltage
2. Reference of current injected into the grid

We need a reference for the voltage across the filter capacitor. Why? Because we have already succeeded in controlling the voltage across with a state feedback controller based on Linear Quadratic Controller.

I ran a free simulation. What that means is I adjusted the modulation index of the dc-ac converter to random values and checked what would be the values of grid voltage, grid current and filter capacitor. All this is for training a machine learning algorithm. And I showed in the previous blog post, the plot of this.

Now, to the next stage. Take the output file and convert it to a .csv file that Pandas can read. Pandas is a data processing package available in Python as part of the PyData library.

This is an extract of the DataFrame which is what you call a database in Pandas:


Time Ammeter_Lfinv Ammeter_Lfgrid Voltmeter_Cfilter Voltmeter_Vgrid inv1trainer_modsignal pwm_carr pwm_s1logic
0 0.00001 -2.968437 -0.003901 -0.095838 0.640844 2.368476e-17 0.200 0.0
1 0.00002 -5.663500 -0.014377 -0.309820 1.217527 2.368476e-17 0.400 0.0
2 0.00003 -8.951290 -0.037764 -0.734868 1.922236 4.946608e-03 0.602 0.0
3 0.00004 -11.932744 -0.070674 -1.276453 2.562736 4.946608e-03 0.800 0.0
4 0.00005 -14.905593 -0.116192 -1.964789 3.203073 9.874666e-03 1.000 0.0

Just the top 5 rows. The data frame has a total of 199999 rows. So that's quite a bit of data.

With this, we are now going to try to create a linear model. What we are trying to do is predict the voltage across the filter capacitor. This will be our output 'y'. The inputs are the grid current which we call x1 (input 1) and the grid voltage x2 (input 2). What we want is:

y = b0 + b1*x1 + b2*x2

If we can achieve this, we have a way to calculate the reference capacitor voltage for any given grid voltage and a reference grid current.

We use linear regression. Linear regression will build exactly the above model with data that we pass to it in order to generate the co-efficients b1 and b2 and the intercept b0. The theory behind it is fairly mathematically intense and maybe I will read about it in detail some day. But in short, linear regression generates these constants by trying to minimize the residual sum of squares for all the data points that we are using to train this model:

RSS = (y1 - b0 - b1*x1_1 - b2*x2_1)^2 + (y2 - b0 - b1*x1_2 - b2*x2_2)^2 + ... +
(yn - b0 - b1*x1_n - b2*x2_n)^2

When there are n samples. We have a total of 199999 samples. But we don't want to use all of them. We want to split them into a training set and a testing set. So, let's do a 70%-30% split - 70% of the data is for training and the rest we will use for testing.

I am using the LinearRegression package available with scikit-learn. As I am getting started with machine learning, I am going to try to keep this simple. I will have to add complexity as I go on. For now, this is a good start.

This is what I do:

y = df['Voltmeter_Cfilter']
x = df[['Ammeter_Lfgrid', 'Voltmeter_Vgrid']]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)
inv_model = LinearRegression()
inv_model.fit(x_train, y_train)

The next part is to check how accurate the model is with test data:

predictions = inv_model.predict(x_test)
plt.figure(figsize=(12,8))
sns.scatterplot(x=y_test, y=predictions)


And this is a scatter plot of the prediction of the capacitor voltage versus the actual voltage from the simulation. Most of the scatter lot is in a straight line which is a good sign. But the bad news is that there is some deviation and that deviation is of the order of magnitude of 50V. The filter capacitor and the grid are separated by a small inductor of the order of around 1 mH. Such a difference in voltage of 50V could produce huge spikes in the current.

The final equation turns out to be:

vcf = -0.01157 + 0.04176*igrid + 1.0587*vgrid

Even before trying to simulate this, I am pretty sure it won't work. But still this has been a good first step as I try to apply my rudimentary machine learning in power electronics. I hope as time goes by I will be able to apply more accurate models that can actually be of use.