Tutorial¶
Requirements¶
Applications¶
The PSCAD Automation Library requires the following tools are installed:
PSCAD (such as PSCAD 5.0)
Python 3.x (such as Python 3.8.3)
PIP - Python’s package manager (included with Python 3.5 and later)
PyWin32 - Python extensions for Microsoft Windows
Installation of the above programs is beyond the scope of this tutorial.
Automation Library¶
You can check which version of the PSCAD Automation Library is installed by executing:
py -m mhi.pscad
If multiple versions of Python have been installed, each installation could have its own copy of the Automation Library, so you may want specify the Python version being queried:
py -3.7 -m mhi.pscad
PSCAD’s “Update Client” will automatically install the Automation Library
into the latest Python 3.x installation.
If you wish to add the Automation Library to a different Python 3.x installation,
you would execute a PIP install
command similar to the following
from the C:\Users\Public\Documents\Manitoba Hydro International\Python\Packages
directory:
py -3.5 -m pip install mhi_pscad-2.2.1-py3-none-any.whl
My First Automation Script¶
Running the Tutorial¶
The following script
is the “simplest” PSCAD
automation script that does something useful.
It:
launches PSCAD,
loads the “Tutorial” workspace,
runs all simulation sets in the “Tutorial” workspace, and
quits PSCAD.
To keep this first example simple, no error checking of any kind is done. Also, no feedback from the simulation is retrieved for the same reason.
#!/usr/bin/env python3
import mhi.pscad
# Launch PSCAD
pscad = mhi.pscad.launch()
# Load the tutorial workspace
pscad.load(r"C:\Users\Public\Documents\PSCAD\5.0\Examples\tutorial\Tutorial.pswx")
# Run all the simulation sets in the workspace
pscad.run_all_simulation_sets()
# Exit PSCAD
pscad.quit()
Many assumptions are being made here.
Certificate licensing is used, and a license certificate can be automatically acquired.
Ensure “Retain certificate” is selected under “Certificate Licensing ➭ Termination Behaviour” and ensure you hold a valid certificate when you exit PSCAD before running the above script.
PSCAD 5.0 or later is installed.
The “Public Documents” directory is located at C:\Users\Public\Documents.
Finding the Tutorial¶
We can relax the last restriction by retrieving the location of “Examples” directory from PSCAD, and retrieving the “Tutorial” workspace relative to that. Doing so will allow the scripts in this tutorial to be downloaded and executed without modification by the reader.
First, add os
to the list of imports,
so we can use the os.path.join()
function.
Then, before the pscad.load()
statement,
retrieve the examples_folder
from PSCAD,
and then append tutorial
to that folder to get the tutorial folder path.
Finally, change the pscad.load()
statement to load "Tutorial.pscx"
,
but with a folder=tutorial_folder
option:
# Locate the tutorial directory
tutorial_dir = os.path.join(pscad.examples_folder, "tutorial")
# Load the tutorial workspace
pscad.load("Tutorial.pswx", folder=tutorial_dir)
See the resulting script
.
Logging¶
Add logging
to the current imports,
and add the logging.basicConfig
statement below:
import mhi.pscad, os, logging
# Log 'INFO' messages & above. Include level & module name.
logging.basicConfig(level=logging.INFO,
format="%(levelname)-8s %(name)-26s %(message)s")
If you now run the script
,
you should see something similar to following output.
The paths, port numbers, and process ID’s may be different:
INFO mhi.pscad.automation Automation server port: 54879
INFO mhi.pscad.common.process Launching C:\Users\aneufeld\projects\PSCAD\v50\PSCAD\Release\PSCAD.exe
INFO mhi.pscad.common.process Args: ['/startup:au', '/port:54879', '/splash:false']
INFO mhi.pscad.automation Connecting to localhost:54879
INFO mhi.pscad.automation.pscad Loading ['C:\\Users\\Public\\Documents\\Pscad\\5.0\\Examples\\tutorial\\Tutorial.pswx']
INFO mhi.pscad.automation.pscad Run all simulation sets
In the next steps, we’ll be adding additional logging to our script.
The logging already built into the automation library can be squelched to warnings and above,
so it is easier to see our own log messages.
After the logging.basicConfig( … )
line, add the following:
# Ignore INFO msgs from automation (eg, mhi.pscad, mhi.pscad.pscad, ...)
logging.getLogger('mhi.pscad').setLevel(logging.WARNING)
LOG = logging.getLogger('main')
Next, add logging lines to print out the location of the “tutorial” directory:
LOG.info("Tutorial directory: %s", tutorial_dir)
If you now run the script
,
you should see something similar to following output.
Again, the path may be different, which is of course the whole point of this exercise:
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
Error Handling¶
If the automation library cannot launch PSCAD,
it will log the reason why to the console and return the value None
.
Other problems may be signalled by an “Exception” being raised.
A well behaved script must catch these exceptions, and properly clean up.
This cleanup should include closing PSCAD.
Replace the commands following the launch of PSCAD with the following,
to create a more fault-tolerant script
:
if pscad:
# Locate the tutorial directory
tutorial_dir = os.path.join(pscad.examples_folder, "tutorial")
LOG.info("Tutorial directory: %s", tutorial_dir)
try:
# Load the tutorial workspace
pscad.load("Tutorial.pswx", folder=tutorial_dir)
# Run all the simulation sets in the workspace
pscad.run_all_simulation_sets()
finally:
# Exit PSCAD
pscad.quit()
else:
LOG.error("Failed to launch PSCAD")
Whether or not an exception occurs during the loading of the workspace
or running all simulation sets, due to the try: ... finally: ...
block,
PSCAD will always be terminated before the script terminates.
Launch Options¶
Minimize PSCAD¶
When PSCAD is started, it normally opens the PSCAD window. During automation, it is sometimes desirable to launch PSCAD with its window “minimized”, reducing the amount “screen flickering”, as well as the chance a stray mouse click could “poison” a running automation.
Add minimized=True
to the launch()
call:
# Launch PSCAD
pscad = mhi.pscad.launch(minimize=True)
Run this new script
.
You should still see log messages in the Python Shell, and PSCAD appear in the Task Bar,
but no PSCAD window should open.
PSCAD Version¶
The launch()
method does a bit of work behind the scenes.
If more than one version of PSCAD is installed, it tries to pick the best version to run.
“Best” in this context means:
not an “Alpha” version, if other choices exist, then
not a “Beta” version, if other choices exist, then
not a 32-bit version, if other choices exist, finally
the “lexically largest” version of the choices that remain.
Instead of letting launch()
choose the version,
the script can specify the exact version to use with the
version=...
and/or x64=...
parameters.
For example, to launch the 64-bit version of PSCAD 5.0, use:
pscad = mhi.pscad.launch(version='5.0', x64=True)
The Automation Library may be queried to determine which versions of PSCAD are installed.
Replacing the launch()
statement with the following will mimic
its selection process:
versions = mhi.pscad.versions()
LOG.info("PSCAD Versions: %s", versions)
# Skip any 'Alpha' versions, if other choices exist
vers = [(ver, x64) for ver, x64 in versions if ver != 'Alpha']
if len(vers) > 0:
versions = vers
# Skip any 'Beta' versions, if other choices exist
vers = [(ver, x64) for ver, x64 in versions if ver != 'Beta']
if len(vers) > 0:
versions = vers
# Skip any 32-bit versions, if other choices exist
vers = [(ver, x64) for ver, x64 in versions if x64]
if len(vers) > 0:
versions = vers
LOG.info(" After filtering: %s", versions)
# Of any remaining versions, choose the "lexically largest" one.
version, x64 = sorted(versions)[-1]
LOG.info(" Selected PSCAD version: %s %d-bit", version, 64 if x64 else 32)
# Launch PSCAD
pscad = mhi.pscad.launch(minimize=True, version=version, x64=x64)
With the above changes to the script
, the output may be:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
Fortran Version¶
In a similar fashion, the Automation Library may be queried for which versions of FORTRAN are installed.
Before the launch()
line, add the following:
# Get all installed FORTRAN compiler versions
fortrans = mhi.pscad.fortran_versions()
LOG.info("FORTRAN Versions: %s", fortrans)
# Skip 'GFortran' compilers, if other choices exist
vers = [ver for ver in fortrans if 'GFortran' not in ver]
if len(vers) > 0:
fortrans = vers
LOG.info(" After filtering: %s", fortrans)
# Order the remaining compilers, choose the last one (highest revision)
fortran = sorted(fortrans)[-1]
LOG.info(" Selected FORTRAN version: %s", fortran)
Then create a collection of settings to pass in to PSCAD in the launch call:
# Launch PSCAD
LOG.info("Launching: %s FORTRAN=%r", version, fortran)
settings = { 'fortran_version': fortran }
pscad = mhi.pscad.launch(minimize=True, version=version, x64=x64,
settings=settings)
With the above changes to the script
, the output may be:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2'
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
Matlab Version¶
In a similar fashion, the Automation Library may be queried for which versions of Matlab are installed. Matlab, unlike FORTRAN, is not required for PSCAD to run, so there may be no versions of Matlab installed.
After the code to select the FORTRAN version, add the following:
# Get all installed Matlab versions
matlabs = mhi.pscad.matlab_versions()
LOG.info("Matlab Versions: %s", matlabs)
# Get the highest installed version of Matlab:
matlab = sorted(matlabs)[-1] if matlabs else ''
LOG.info(" Selected Matlab version: %s", matlab)
Then add matlab_version=matlab
to the settings
:
# Launch PSCAD
LOG.info("Launching: %s FORTRAN=%r Matlab=%r", version, fortran, matlab)
settings = { 'fortran_version': fortran, 'matlab_version': matlab }
With the above changes to the script
, the output may be:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
PSCAD Settings¶
To ensure reliable, repeatable tests, the automation library may instruct PSCAD to use the default settings, ignoring any settings stored in the user profile:
pscad = mhi.pscad.launch(load_user_profile=False)
In addition to passing in settings during launch,
the pscad.settings()
method
may be used to change any application settings to the desired value.
The following script
may be used to determine
what settings exist, and their current values:
#!/usr/bin/env python3
import mhi.pscad
pscad = mhi.pscad.launch(minimize=True)
for key, value in sorted(pscad.settings().items()):
print("%33s: %s" % (key, value))
pscad.quit()
And produces output similar to:
AvoidLocalExec: YES
DetectedCoreCount:
EMTDC_Version:
Folder:
LCP_MaxConcurrentExec: 8
LCP_Version:
LanguageDir: $(HomeDir)\Languages\en-us
MEDIC_Version:
MaxConcurrentSim: 8
MenuTooltipFile: $(LanguageDir)\Tooltips\MenuTooltips.xml
SocketPortBase: 30000
TerminateInactiveHelper: NO
active_graphics: 2_S_
adv_changed_color: red
adv_excluded_color: blue
adv_highlight_color: #ffff7538
adv_included_color: lime
agent_show: True
autosave_interval: NO_ACTION
backup_enable: True
backup_folder: $(LocalDir)\FileBackups
backup_freq: 60
⋮ ⋮ ⋮ ⋮
To change settings from their defaults, pass one or more key=value
arguments
to the pscad.settings()
method:
pscad.settings(MaxConcurrentSim=8, LCP_MaxConcurrentExec=8)
Projects¶
To run all loaded projects one at a time, we first obtain a list of all projects.
From this list, we filter out any libraries, which are not runnable.
Then, for the remaining Project
cases,
we call the Project.run()
method on each one,
in succession.
Replace the pscad.run_all_simulation_sets()
line with the following:
# Get a list of all projects
projects = pscad.projects()
# Filter out libraries; only keep cases.
cases = [prj for prj in projects if prj['type'] == 'Case']
# For each case ...
for case in cases:
project = pscad.project(case['name'])
LOG.info("Running '%s' (%s)", case['name'], case['description'])
project.run();
LOG.info("Run '%s' complete", case['name'])
This script
would produce:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
INFO main Running 'chatter' (Simple case with chatter elimination)
INFO main Run 'chatter' complete
INFO main Running 'fft' (Harmonic Impedance and FFT)
INFO main Run 'fft' complete
INFO main Running 'inputctrl' (Input Control Components)
INFO main Run 'inputctrl' complete
INFO main Running 'interpolation' (Simple case illustrating interpolation)
INFO main Run 'interpolation' complete
INFO main Running 'legend' (Use of macros)
INFO main Run 'legend' complete
INFO main Running 'vdiv' (Single Phase Voltage Divider)
INFO main Run 'vdiv' complete
INFO main Running 'simpleac' (A Simple AC Power System)
INFO main Run 'simpleac' complete
INFO main Running 'multirun' (A Simple Multiple Run Example)
INFO main Run 'multirun' complete
INFO main Running 'pagearray' (Page Inside a Page, Arrays)
INFO main Run 'pagearray' complete
Simulation Sets¶
When a workspace contains multiple projects, they may be required to run together or in a particular sequence. A simulation set is often used to control the collection of projects.
Instead of blindly running all projects in the workspace, or simply all simulation sets, we may retrieve the list of simulations sets and run each simulation set individually, under the control of our script. If no simulation sets are found, we can fall back to running each project separately.
Replace the code we just added, with this code instead:
# Get the list of simulation sets
sim_sets = pscad.simulation_sets()
if len(sim_sets) > 0:
LOG.info("Simulation sets: %s", sim_sets)
# For each simulation set ...
for sim_set_name in sim_sets:
# ... run it
LOG.info("Running simulation set '%s'", sim_set_name)
sim_set = pscad.simulation_set(sim_set_name)
sim_set.run()
LOG.info("Simulation set '%s' complete", sim_set_name)
else:
# Get a list of all projects
projects = pscad.projects()
# Filter out libraries; only keep cases.
cases = [prj for prj in projects if prj['type'] == 'Case']
# For each case ...
for case in cases:
project = pscad.project(case['name'])
LOG.info("Running '%s' (%s)", case['name'], case['description'])
project.run();
LOG.info("Run '%s' complete", case['name'])
This version of the script
runs all projects simultaneously,
producing the following output:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
INFO main Simulation sets: ['default']
INFO main Running simulation set 'default'
INFO main Simulation set 'default' complete
If, instead of loading "Tutorial.pswx"
,
we only loaded the "vdiv.pscx"
project:
# Load only the 'voltage divider' project
pscad.load("vdiv.pscx", folder=tutorial_dir)
there are no simulation sets,
so the else:
path is taken in our script
,
and the vdiv
project is run:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
INFO main Running 'vdiv' (Single Phase Voltage Divider)
INFO main Run 'vdiv' complete
Event Monitoring¶
Event Handlers¶
The Automation Library will allow you to observe the communications between the automation library and the PSCAD process.
At the top of the file, add the following “Handler” class:
class Handler:
def send(self, evt):
if evt:
LOG.info("%s", evt)
else:
LOG.info("TICK")
def close(self):
pass
Following the successful launch of PSCAD, we can create an instance of our Handler
class,
and attach it to the PSCAD instance. After the if pscad:
, add the following three lines:
if pscad:
handler = Handler()
pscad.subscribe('load-events', handler)
pscad.subscribe('build-events', handler)
After downgrading some of the earlier LOG.info(...)
messages to LOG.debug(...)
message,
if we run this script
, we might see:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
INFO main {'event': 'LoadEvent', 'status': 'BEGIN', 'file-type': 'files'}
INFO main {'event': 'LoadEvent', 'status': 'BEGIN', 'file-type': 'project', 'file': 'C:\\Users\\Public\\Documents\\Pscad\\5.0\\Examples\\tutorial\\vdiv.pscx'}
INFO main {'event': 'LoadEvent', 'status': 'END', 'file-type': 'project', 'file': 'C:\\Users\\Public\\Documents\\Pscad\\5.0\\Examples\\tutorial\\vdiv.pscx', 'name': 'vdiv', 'type': 'Case', 'Description': 'Single Phase Voltage Divider'}
INFO main {'event': 'LoadEvent', 'status': 'END', 'file-type': 'files'}
INFO main Running 'vdiv' (Single Phase Voltage Divider)
INFO main {'event': 'BuildEvent', 'name': 'Workspace', 'status': 'BEGIN'}
INFO main {'event': 'BuildEvent', 'name': 'Project Builder', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Compile', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Compile', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Solver', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Solver', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'EMTDC MAKE', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'EMTDC MAKE', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Builder', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Mediator', 'status': 'BEGIN', 'project': 'vdiv', 'rank': 1}
INFO main {'event': 'BuildEvent', 'name': 'EMTDC RUN', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Mediator', 'status': 'END', 'project': 'vdiv', 'rank': 1}
INFO main {'event': 'BuildEvent', 'name': 'Workspace', 'status': 'END'}
INFO main Run 'vdiv' complete
As messages are received from PSCAD, the automation library sends the message to each registered handler,
by calling handler.send(msg)
.
The handler can do pretty much whatever it wants with the message.
If it consumes the message, and doesn’t want any subsequent handler from seeing it,
the handler should return True
.
After a period of time when no messages have been sent back from PSCAD,
the automation library also sends a blank message to the handler, by calling handler.send(None)
.
This allows the handler a chance to do additional processing.
For instance, the Automated Test Suite’s ProgressHandler
uses those opportunities
to send a get-run-status
command to the various projects, in order to track % complete
.
Removing Handlers¶
If the script decides a certain handler is no longer required,
the script can remove it by calling pscad.unsubscribe( eventname )
.
finally:
# Exit PSCAD
pscad.unsubscribe('build-events')
pscad.unsubscribe('load-events')
pscad.quit()
Automatic Removal¶
Since the handler is receiving messages from PSCAD,
it is usually in the best position to determine when a particular process is complete.
The handler can indicate this to the automation library, by returning the special value StopIteration
,
or by raising the StopIteration
exception.
When this happens, the automation library will automatically remove the handler.
For example, the Load
process is complete when a load event is found
with a file-type
of files
and a status
of END
.
Change the Handler’s send()
method to the following code:
def send(self, evt):
if evt:
LOG.info("%s", evt)
if evt['status'] == 'END' and evt.get('file-type', None) == 'files':
raise StopIteration
else:
LOG.info("TICK")
When this new script
is run, this output results:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
INFO main {'event': 'LoadEvent', 'status': 'BEGIN', 'file-type': 'files'}
INFO main {'event': 'LoadEvent', 'status': 'BEGIN', 'file-type': 'project', 'file': 'C:\\Users\\Public\\Documents\\Pscad\\5.0\\Examples\\tutorial\\vdiv.pscx'}
INFO main {'event': 'LoadEvent', 'status': 'END', 'file-type': 'project', 'file': 'C:\\Users\\Public\\Documents\\Pscad\\5.0\\Examples\\tutorial\\vdiv.pscx', 'name': 'vdiv', 'type': 'Case', 'Description': 'Single Phase Voltage Divider'}
INFO main {'event': 'LoadEvent', 'status': 'END', 'file-type': 'files'}
INFO main Running 'vdiv' (Single Phase Voltage Divider)
INFO main {'event': 'BuildEvent', 'name': 'Workspace', 'status': 'BEGIN'}
INFO main {'event': 'BuildEvent', 'name': 'Project Builder', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Compile', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Compile', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Solver', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Solver', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'EMTDC MAKE', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'EMTDC MAKE', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Project Builder', 'status': 'END', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Mediator', 'status': 'BEGIN', 'project': 'vdiv', 'rank': 1}
INFO main {'event': 'BuildEvent', 'name': 'EMTDC RUN', 'status': 'BEGIN', 'project': 'vdiv'}
INFO main {'event': 'BuildEvent', 'name': 'Mediator', 'status': 'END', 'project': 'vdiv', 'rank': 1}
INFO main {'event': 'BuildEvent', 'name': 'Workspace', 'status': 'END'}
INFO main Run 'vdiv' complete
WARNING mhi.pscad.automation.pscad Not currently subscribed to load-events events
The “Not currently subscribed to load-events events” message is generated because we are still trying to unsubscribe to “load-events”, but the handler uninstalled itself.
Build Events¶
Build Events are generated when PSCAD builds and runs cases. A build event handler might be installed just before executing the run command, and removed just after the run command finished. Code to do thing might look like this:
handler = BuildEventHandler()
pscad.subscribe("build-events", handler)
project.run()
pscad.unsubscribe("build-events")
This is a lot of boiler-plate code.
As a convenience, the automation library will perform the subscribe
and unsubscribe
calls itself,
if the handler is passed to the run( )
command directly:
project.run( BuildEventHandler() )
Add an import for mhi.pscad.handler
:
import mhi.pscad, os, logging
import mhi.pscad.automation.handler
Replace the Handler
code from the previous section with the following:
class BuildEventHandler(mhi.pscad.automation.handler.BuildEvent):
def _build_event(self, phase, status, project, elapsed, **kwargs):
LOG.info("BuildEvt: [%s] %s/%s %.3f", project, phase, status, elapsed)
Here, we are extending a standard BuildEvent
handler.
Its send()
method already looks for build event messages, and forwards them to the _build_event()
method,
as well as looks for matching BEGIN
and END
messages,
and returns StopIteration
when the final END
message is found.
This standard BuildEvent
handler in turn extends an AbstractHandler
,
which implements the required do-nothing close()
method.
As such, most of the required work has already been done for us.
We just need to override _build_event()
.
Remove the previous pscad.subscribe( )
call,
and replace the project.run()
call with:
project.run(BuildEventHandler());
When this script
is run, the following output is produced:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
INFO main Running 'vdiv' (Single Phase Voltage Divider)
INFO main BuildEvt: [None] Workspace/BEGIN 0.187
INFO main BuildEvt: [vdiv] Project Builder/BEGIN 0.352
INFO main BuildEvt: [vdiv] Project Compile/BEGIN 0.354
INFO main BuildEvt: [vdiv] Project Compile/END 0.892
INFO main BuildEvt: [vdiv] Project Solver/BEGIN 1.144
INFO main BuildEvt: [vdiv] Project Solver/END 1.185
INFO main BuildEvt: [vdiv] EMTDC MAKE/BEGIN 1.812
INFO main BuildEvt: [vdiv] EMTDC MAKE/END 4.444
INFO main BuildEvt: [vdiv] Project Builder/END 4.576
INFO main BuildEvt: [vdiv] Mediator/BEGIN 4.820
INFO main BuildEvt: [vdiv] EMTDC RUN/BEGIN 4.832
INFO main BuildEvt: [vdiv] Mediator/END 5.280
INFO main BuildEvt: [None] Workspace/END 5.281
INFO main Run 'vdiv' complete
Our handler is just displaying the events as they occur.
It could do more interesting things.
For instance, when it receives a BEGIN
message, it could record the elapsed
time;
when it receives a matching END
message,
it could subtract the elapsed
time from the time it recorded earlier,
giving the time required for each task:
class BuildEventHandler(mhi.pscad.automation.handler.BuildEvent):
def __init__(self):
super().__init__()
self._start = {}
def _build_event(self, phase, status, project, elapsed, **kwargs):
key = (project, phase)
if status == 'BEGIN':
self._start[key] = elapsed
else:
sec = elapsed - self._start[key]
name = project if project else '[All]'
LOG.info("%s %s: %.3f sec", name, phase, sec)
Running this revised script
script would produce:
INFO main PSCAD Versions: [('4.6.3', True), ('5.0', True), ('Beta', True)]
INFO main After filtering: [('4.6.3', True), ('5.0', True)]
INFO main Selected PSCAD version: 5.0 64-bit
INFO main FORTRAN Versions: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main After filtering: ['GFortran 4.2.1', 'GFortran 4.6.2']
INFO main Selected FORTRAN version: GFortran 4.6.2
INFO main Matlab Versions: []
INFO main Selected Matlab version:
INFO main Launching: 5.0 FORTRAN='GFortran 4.6.2' Matlab=''
INFO main Tutorial directory: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial
INFO main Running 'vdiv' (Single Phase Voltage Divider)
INFO main vdiv Project Compile: 0.770 sec
INFO main vdiv Project Solver: 0.122 sec
INFO main vdiv EMTDC MAKE: 2.624 sec
INFO main vdiv Project Builder: 5.004 sec
INFO main vdiv Mediator: 0.482 sec
INFO main [All] Workspace: 6.075 sec
INFO main Run 'vdiv' complete
The handler can store information collected during the run, that may be accessed afterwards. Of course, to do so, you would need to hold onto a reference to the handler:
handler = BuildEventHandler()
project.run( handler )
info = handler.get_interesting_data()
Build & Run Messages¶
Build Messages¶
When PSCAD builds a project, the build messages are recorded. The script can retrieve these build messages.
After the project.run(…)
line, add the following:
project.run(BuildEventHandler());
LOG.info("Run '%s' complete", case['name'])
messages = project.messages()
for msg in messages:
print("%s %s %s" % (msg.scope, msg.status, msg.text))
This script
would produce:
⋮ ⋮ ⋮ ⋮
INFO main Run 'vdiv' complete
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\LicenseConfig.cfg
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\Main.dta
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\Main.f
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\Main.o
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\Station.dta
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\Station.f
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\Station.o
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv.exe
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv.mak
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv.map
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv_1.config
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv_30023.bat
vdiv normal Removed file: C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv_mak.bat
vdiv normal Removed 13 files
vdiv normal Completed cleaning of temporary files and data.
vdiv normal Begin compiling with PSCAD Version 5.0
vdiv normal Generating 'C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv.map'.
vdiv normal The total number of live nodes: 1
vdiv normal Coding .mak file: 'C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\\vdiv.mak'
vdiv normal Time for Compile: 46ms Make: 0ms
vdiv normal Solve Time = 47ms
vdiv normal Will execute (1): call C:\Program Files (x86)\GFortran\4.6\bin\gf46vars.bat
vdiv normal Will execute (1): call "C:\Program Files (x86)\GFortran\4.6\bin\gf46vars.bat"
vdiv normal Will execute (2): make -j -f vdiv.mak
vdiv normal Will execute (2): "C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\vdiv_mak.bat"
vdiv normal Creating EMTDC executable...
vdiv normal C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46>call "C:\Program Files (x86)\GFortran\4.6\bin\gf46vars.bat"
vdiv normal Compiling 'Station.f' into object code.
vdiv normal Compiling 'Main.f' into object code.
vdiv normal Linking objects and libraries into binary 'vdiv.exe'
vdiv normal Finished compiling with PSCAD Version 5.0
Here, we are just extracting the scope, status, and text of the messages. Other fields, such as label and component references could also be extracted.
Run Messages¶
After EMTDC has run the project, the run messages may also be retrieved. Unlike the build messages, the run messages are returned as an unstructured blob of text.
Immediately after the above code, add the following lines:
print("-"*60)
output = project.output()
print(output)
When run, this change to the script
adds the run messages to the output:
⋮ ⋮ ⋮ ⋮
vdiv normal C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46>call "C:\Program Files (x86)\GFortran\4.6\bin\gf46vars.bat"
vdiv normal Compiling 'Station.f' into object code.
vdiv normal Compiling 'Main.f' into object code.
vdiv normal Linking objects and libraries into binary 'vdiv.exe'
vdiv normal Finished compiling with PSCAD Version 5.0
------------------------------------------------------------
Initializing Simulation Run
Executing > "C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46\\vdiv_30009.bat"
Communications: Connection established.
C:\Users\Public\Documents\Pscad\5.0\Examples\tutorial\vdiv.gf46>call "C:\Program Files (x86)\GFortran\4.6\bin\gf46vars.bat"
Requested locale = english-us
Current locale = English_United States.1252
EMTDC(tm) Version 5.00 R#93051001
****
* EMTDC for PSCAD v5: Build# 20200721
****
Time Summary: Start Date: 10-16-2018
Time Summary: Start Time: 13:10:06
The actual plot step: '250.000000'
Time Summary: Stop Time: 13:10:06
Time Summary: Elapsed Time: 00:00:00
Time Summary: Total CPU Time: 47ms.
Terminating connection.
EMTDC run completed.