-
Notifications
You must be signed in to change notification settings - Fork 490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lightsim2grid: changed checks for lightsim2grid #1455
Conversation
… option for lightsim2grid that decides whether to use it silently, added lightsim2grid to the GitHub CI for Python 3.9
Codecov Report
@@ Coverage Diff @@
## develop #1455 +/- ##
===========================================
- Coverage 87.28% 87.15% -0.13%
===========================================
Files 168 168
Lines 17225 17231 +6
===========================================
- Hits 15034 15017 -17
- Misses 2191 2214 +23
Continue to review full report at Codecov.
|
…pf for distributed_slack only. this reduced the number of failing tests but still WIP
# first
net = example_simple()
pp.runpp(net, distributed_slack=True) # works
pp.runpp(net, distributed_slack=True, numba=False) # LoadFlowNotConverged
#second
pp.runpp(net, distributed_slack=True) # works
pp.runpp(net, distributed_slack=True, numba=False, init="results") # works
#third
net2 = example_simple()
pp.runpp(net, distributed_slack=True) # works
pp.runpp(net2, distributed_slack=True, lightsim2grid=False) # works
assert_res_equal(net, net2) # works Some observations about the failing tests: For comparison, and because of the implementation of newtonpf_new, here we set the parameter distributeed_slack to True. The load flow fails (solver does not converge in lightsim2grid) in many instances. First: the example demonstrates how the calculation works for numba=True and fails for numba=False. lightsim2grid is used in both instances. Second: the example demonstrates that with init="results" solver in lightsim2grid converges even with numba=False. Must be because the algorithm doesn't even enter the loop and just exits with a valid solution right away. Third: results match with lightsim2grid and without lightsim2grid. @BDonnot can you please help with debugging? Can you reproduce this behavior? Do you have an idea, what could cause this error? can it be something related to types etc.? Roman |
Hello, Can I have a way to import the "example_simple()" function ? I cannot reproduce the bug here without this function :-/ |
Hi @BDonnot , the example_simple function is in pandapower.networks (should be imported extra): import pandapower as pp
import pandapower.networks
net = pp.networks.example_simple() Roman |
Hello, I cannot reproduce the problem on my dev machine (ubuntu 20.04, python3.8) Can you launch: print("now with example provided")
import pandapower as pp
import lightsim2grid
import pdb
import pandas as pd
from pandapower.networks import example_simple
import numpy as np
import pandas as pd
print(f"{np.__version__ = }")
print(f"{pd.__version__ = }")
print(f"{lightsim2grid.__version__ = }")
print(f"{pp.__version__ = }")
net = example_simple()
pp.runpp(net, distributed_slack=True) # works
pp.runpp(net, distributed_slack=True, numba=False) # LoadFlowNotConverged
assert net.converged
#second
pp.runpp(net, distributed_slack=True) # works
pp.runpp(net, distributed_slack=True, numba=False, init="results") # works
assert net.converged
#third
net2 = example_simple()
pp.runpp(net, distributed_slack=True) # works
pp.runpp(net2, distributed_slack=True, lightsim2grid=False) # works
assert net2.converged
# assert_res_equal(net, net2) # works # -> not imported so skipping it
assert net.res_gen.equals(net2.res_gen)
assert net.res_load.equals(net2.res_load)
assert net.res_line.equals(net2.res_line)
print("everything match !") And give me the output ? Also, can you give me your setup (os and python version please) FYI for me the above script gives:
Also it's unclear for me what you want to test exactly. Your are totally correct, lightsim2grid does not rely on numba at all. If the flag "numba=True" changes something, then it's probably something given as input of lightsim2grid, for example the init voltages. |
I have this output: now with example provided
np.__version__ = '1.21.5'
pd.__version__ = '1.3.5'
lightsim2grid.__version__ = '0.6.0'
pp.__version__ = '2.7.1.dev1'
Traceback (most recent call last):
File "C:\ProgramData\Miniconda3\envs\pandapower\lib\site-packages\IPython\core\interactiveshell.py", line 3251, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-2-12c50577f436>", line 15, in <module>
pp.runpp(net, distributed_slack=True, numba=False) # LoadFlowNotConverged
File "C:\Users\rbolgaryn\repos\pandapower\pandapower\run.py", line 231, in runpp
_powerflow(net, **kwargs)
File "C:\Users\rbolgaryn\repos\pandapower\pandapower\powerflow.py", line 86, in _powerflow
_ppci_to_net(result, net)
File "C:\Users\rbolgaryn\repos\pandapower\pandapower\powerflow.py", line 188, in _ppci_to_net
raise LoadflowNotConverged("Power Flow {0} did not converge after "
pandapower.powerflow.LoadflowNotConverged: Power Flow nr did not converge after 10 iterations! |
My Python version is 3.9 on Windows 10. The difference in behavior is very confusing. |
Oh ok it was my mistake, I did not installed your pandapower version by the default one and now i'm able to reproduce it and I get the error:
I'll try to see what is the cause of it |
I also see the same behavior in linux, with Python 3.9.9: now with example provided |
As i thought, the difference is in pandapower and not in lightsim2grid. The When it is initialized with For example, just by printing it: def _run_ac_pf_without_qlims_enforced(ppci, options):
makeYbus, pfsoln = _get_numba_functions(ppci, options)
baseMVA, bus, gen, branch, ref, pv, pq, _, _, V0, ref_gens = _get_pf_variables_from_ppci(ppci)
ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, options, makeYbus, baseMVA, bus, branch)
# compute complex bus power injections [generation - load]
Sbus = _get_Sbus(ppci, options["recycle"])
# run the newton power flow
import numpy as np
print(f"{np.sum(np.abs(Ybus)) = }") # the line I added
[rest of the function that i do not copy !] And this simple snippet works: import pandapower as pp
from pandapower.networks import example_simple
pp.runpp(net, distributed_slack=True, numba=False, lightsim2grid=True) # works only with the "print" case
pp.runpp(net, distributed_slack=True, numba=True, lightsim2grid=True) # works in both cases I have absolutely no idea why printing something changes the behaviour... |
I just tried with (so it does not print anything): def _run_ac_pf_without_qlims_enforced(ppci, options):
makeYbus, pfsoln = _get_numba_functions(ppci, options)
baseMVA, bus, gen, branch, ref, pv, pq, _, _, V0, ref_gens = _get_pf_variables_from_ppci(ppci)
ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, options, makeYbus, baseMVA, bus, branch)
# compute complex bus power injections [generation - load]
Sbus = _get_Sbus(ppci, options["recycle"])
# run the newton power flow
import numpy as np
my_sum = np.sum(np.abs(Ybus))
if options["lightsim2grid"] and not options["distributed_slack"]:
V, success, iterations, J, Vm_it, Va_it = newton_ls_old(Ybus, Sbus, V0, pv, pq, ppci, options)
elif options["lightsim2grid"]:
V, success, iterations, J, Vm_it, Va_it = newton_ls(Ybus, Sbus, V0, ref, pv, pq, ppci, options)
else:
V, success, iterations, J, Vm_it, Va_it = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options)
[etc.] And it works too, I don't know what happens when the Ybus matrix is filled with python and not with numba :-/ Code seems to work, but data is somehow not correct... Can you try if your tests pass with that ? (so that we can maybe further look at the issue when it's not done...) |
I can confirm that the snippet also works for me |
It's still super weird, but I guess keeping it this way won't hurt pandapower too much. I don't have any clue on what this does under the hood to solve the issue... |
There are still some failing tests, especially in the distributed slack functionality. I will take a look further |
Let me know if I can be of further assistance |
Can you send me a log or something on why it fails? |
you should be able to see in the test details below in a moment |
Hummm this appear to be an issue with 3.7 and the f string in lightsim2grid. Can you remove 3.7 to see if other tests break? I'll have a look tomorrow |
It seems to work for 3.8 and 3.9! |
Great news. We're almost there! It's super weird it does nor work with 3.7 because... Well I compiled the package on python 3.7... Anyway, I'll have a look to confirm that tomorrow and fix it if there's issue. Thanks for all the efforts and patience |
Thank you too! I will rollback the python versions for now, and skip lightsim2grid for python 3.6. |
… (to avoid it being installed in GitHub Actions). After dropping 3.6 we will include it in all but then uninstall for the tests for some environments
I found out what the bug was in python3.7. I fixed it and added a test that the package can be loaded in the CI (somehow I did not do this test...) I'll release a new version (0.6.1.post1) this morning. I confirm that I will not add python 3.6 support for lightsim2grid :
(from https://www.python.org/downloads/release/python-3615/ ) |
@rbolgaryn It should work with 3.7 now :-) (release done) |
Thank you very much, @BDonnot ! |
Here some timings (on a Win10 laptop, Python 3.9, lightsim2grid built with cmake according to the guide from the documentation): import pandapower as pp
import pandapower.networks
# case9241pegase:
net = pp.networks.case9241pegase()
pp.runpp(net, numba=True, lightsim2grid=False)
# without lightsim2grid
%timeit pp.runpp(net, numba=True, lightsim2grid=False)
340 ms ± 13.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
net._ppc["et"]
0.313649500021711
# with lightsim2grid
%timeit pp.runpp(net, numba=True, lightsim2grid=True)
127 ms ± 2.66 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
net._ppc["et"]
0.10265469993464649
# case39:
net = pp.networks.case39()
# without lightsim2grid:
%timeit pp.runpp(net, numba=True, lightsim2grid=False)
9.02 ms ± 60.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
net._ppc["et"]
0.0033928999910131097
# with lightsim2grid:
%timeit pp.runpp(net, numba=True, lightsim2grid=True)
8.26 ms ± 236 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
net._ppc["et"]
0.0023731000255793333
# case 118:
net = pp.networks.case118()
# without lightsim2grid:
%timeit pp.runpp(net, init="flat", numba=True, lightsim2grid=False)
8.2 ms ± 413 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
net._ppc["et"]
0.0032513999613001943
# with lightsim2grid:
%timeit pp.runpp(net, init="flat", numba=True, lightsim2grid=True)
6.54 ms ± 166 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
net._ppc["et"]
0.002079599886201322 Seems that the benefit in the performance is there, but not in the same magnitude as shown in benchmarks of lightsim2grid. The explanation is probably that the overhead in pandapower is still quite seizable, and it weighs on the total execution time (the performance boost for a small grid is not very high, but with a large grid is twice as fast - supports this point). The larger the grid, the higher the benefit will be that a pandapower user will notice. Dear @BDonnot , do the timings look reasonable to you? EDIT: when using snakeviz for profiling, the timings look different: with pegase grid, the difference in newton is 196 ms vs. 41 ms, (4,8 times) and the overall time difference is 272 ms vs. 91 ms (3 times). |
What you showed above is that it takes lightsim2grid 0.04s instead of 0.22 (so the speed up for the solver part is more than 5x) And probably some speed can be gained if the solver isn't initialized each time on lightsim2grid. I'll run some benchmarks on my machine with this branch to see if there's some things to win still. Apparently though a big amount of time is spent not in the solver but in the elsewhere in the code (at least in this very large example you provided) I'm also curious where the time is spent in newton_pf_new. But I'm not surprised that 40 ms is taken to run an ac powerflow on a system with approx 10k buses |
There are still huge penalty in the timings there. For example if I launch this code: import pandapower as pp
import pandapower.networks
import time
import sys
nb_repeat = 10
# case9241pegase:
for nm_ in ["case39", "case118", "case9241pegase"]:
net = getattr(pp.networks, nm_) () #.case9241pegase()
# "compile" it with numba (first call always slower)
pp.runpp(net, init="flat", numba=True, lightsim2grid=False)
print(f"for {nm_}")
beg_ = time.process_time()
for _ in range(nb_repeat):
pp.runpp(net, init="flat", numba=True, lightsim2grid=False)
end_ = time.process_time()
print(f"\t without ls: {1000. * (end_- beg_) / nb_repeat :.2f} ms")
# without lightsim2grid
beg_ = time.process_time()
for _ in range(nb_repeat):
pp.runpp(net, init="flat", numba=True, lightsim2grid=True)
end_ = time.process_time()
print(f"\t with ls: {1000. * (end_- beg_) / nb_repeat:.2f} ms") And I modify the "newton_pf" function just print the time spent in "solver.solve" like this: def newtonpf_new(Ybus, Sbus, V0, ref, pv, pq, ppci, options):
import time
super_beg = time.perf_counter()
max_iteration = options["max_iteration"]
tolerance_pu = options['tolerance_mva'] # / ppci["baseMVA"]
try:
# lazy import for earlier pandapower version (without distributed slack):
from pandapower.pypower.idx_bus import SL_FAC
# contribution factors for distributed slack:
slack_weights = ppci['bus'][:, SL_FAC].astype(np.float64)
except ImportError:
# earlier version of pandapower
warnings.warn("You are using a pandapower version that does not support distributed slack. We will attempt to "
"replicate this with lightsim2grid.")
ref, slack_weights = _isolate_slack_ids(Sbus, pv, pq)
# initialize the solver and perform some sanity checks
solver = _get_valid_solver(options, Ybus)
# do the newton raphson algorithm
beg_ = time.perf_counter()
solver.solve(Ybus, V0, Sbus, ref, slack_weights, pv, pq, max_iteration, tolerance_pu)
end_ = time.perf_counter()
# extract the results
Va = solver.get_Va()
Vm = solver.get_Vm()
V = Vm * np.exp(1j * Va)
J = solver.get_J()
converged = solver.converged()
iterations = solver.get_nb_iter()
print(f"\t\tsolver.solve : {1000. * (end_ - beg_) :.2f} ms")
Vm_it = None
Va_it = None
super_end = time.perf_counter()
print(f"\t\ttotal time : {1000. * (super_end - super_beg) :.2f} ms")
return V, converged, iterations, J, Vm_it, Va_it I get the following results:
So basically, the time spent to perform the powerflow (for case39 for example) is around 0.2ms (eg 0.0002456940710544586 s) while the total time seen from pandapower is 15ms. The time I measure in This means that pandapower is taking 14.5ms to do other things that I don't really know. This is really suprising Also, what is exactly measured by |
Indeed, pandapower spends a lot of time with utility functions. For a small grid, the "productive time" constitutes about 2-2.5 % (over 10 % without lightsim2grid). For the pegase grid, lightsim2grid constitutes about 38 % of the total time. For comparison, without lightsim2grid it is over 76 %.
Absolutely |
…tion); added a test for lightsim2grid option; raise NotImplementedError if lightsim2grid == True but is not supported (set to False silently if "auto")
we decided for now to not include lightsim2grid, it makes it easier to test with/without in GitHubActions. Also we will accumulate more practical testing over time. |
Up to you :-) |
…anged the behavior to raise error if the option if True instead of "auto" but is not implemented
# Conflicts: # .github/workflows/github_test_action.yml
added default "auto" option for lightsim2grid that decides whether to use it silently, added lightsim2grid to the GitHub CI for Python 3.9