Skip to content

Commit

Permalink
Merge pull request #196 from cuihantao/develop
Browse files Browse the repository at this point in the history
Overhaul for hot swapping `pycode`.
  • Loading branch information
cuihantao committed Oct 6, 2021
2 parents 886b694 + 966f406 commit a89bfbd
Show file tree
Hide file tree
Showing 8 changed files with 764 additions and 2,841 deletions.
2 changes: 1 addition & 1 deletion andes/models/__init__.py
Expand Up @@ -36,6 +36,6 @@
'REPCA1', 'WTDTA1', 'WTDS', 'WTARA1', 'WTPTA1', 'WTTQA1', 'WTARV1',
'REGCVSG', 'REGCVSG2']),
('distributed', ['PVD1', 'ESD1', 'EV1', 'EV2', 'DGPRCT1', 'DGPRCTExt']),
('experimental', ['PI2', 'TestDB1', 'TestPI', 'TestLagAWFreeze', 'FixedGen']),
('coi', ['COI']),
# ('experimental', ['PI2', 'TestDB1', 'TestPI', 'TestLagAWFreeze', 'FixedGen']),
])
4 changes: 4 additions & 0 deletions andes/models/line.py
Expand Up @@ -65,21 +65,25 @@ def __init__(self):
)
self.b1 = NumParam(default=0.0,
info="from-side susceptance",
y=True,
tex_name='b_1',
unit='p.u.',
)
self.g1 = NumParam(default=0.0,
info="from-side conductance",
y=True,
tex_name='g_1',
unit='p.u.',
)
self.b2 = NumParam(default=0.0,
info="to-side susceptance",
y=True,
tex_name='b_2',
unit='p.u.',
)
self.g2 = NumParam(default=0.0,
info="to-side conductance",
y=True,
tex_name='g_2',
unit='p.u.',
)
Expand Down
99 changes: 66 additions & 33 deletions andes/system.py
Expand Up @@ -30,7 +30,7 @@
from andes.utils.tab import Tab
from andes.utils.misc import elapsed
from andes.utils.paths import confirm_overwrite
from andes.utils.paths import get_config_path, get_dot_andes_path
from andes.utils.paths import get_config_path, andes_root
from andes.utils.paths import get_pycode_path, get_pkl_path
from andes.core import Config, Model, AntiWindup
from andes.io.streaming import Streaming
Expand All @@ -40,7 +40,6 @@

logger = logging.getLogger(__name__)
dill.settings['recurse'] = True
pycode = None


class ExistingModels:
Expand Down Expand Up @@ -214,6 +213,7 @@ def reload(self, case, **kwargs):
"""
Reload a new case in the same System object.
"""

self.options.update(kwargs)
self.files.set(case=case, **kwargs)
# TODO: clear all flags and empty data
Expand Down Expand Up @@ -368,8 +368,7 @@ def _finalize_pycode(self, pycode_path):
logger.info('Saved generated pycode to "%s"', pycode_path)

# RELOAD REQUIRED as the generated Jacobian arguments may be in a different order
if pycode:
self._call_from_pycode()
self._call_from_pycode()

def _find_stale_models(self):
"""
Expand Down Expand Up @@ -639,18 +638,21 @@ def _init_numba(self, models: OrderedDict):
"""
Helper function to compile all functions with Numba before init.
"""
if self.config.numba:
use_parallel = bool(self.config.numba_parallel)
use_cache = bool(self.config.numba_cache)
nopython = bool(self.config.numba_nopython)
if not self.config.numba:
return

logger.info("Numba compilation initiated, parallel=%s, cache=%s.",
use_parallel, use_cache)
for mdl in models.values():
mdl.numba_jitify(parallel=use_parallel,
cache=use_cache,
nopython=nopython,
)
use_parallel = bool(self.config.numba_parallel)
use_cache = bool(self.config.numba_cache)
nopython = bool(self.config.numba_nopython)

logger.info("Numba compilation initiated, parallel=%s, cache=%s.",
use_parallel, use_cache)

for mdl in models.values():
mdl.numba_jitify(parallel=use_parallel,
cache=use_cache,
nopython=nopython,
)

def init(self, models: OrderedDict, routine: str):
"""
Expand Down Expand Up @@ -1410,38 +1412,51 @@ def _call_from_pkl(self):
Helper function for loading ModelCall from pickle file.
"""

self.calls = self._load_pkl()
loaded = False
any_calls = self._load_pkl()

if self.calls is not None:
loaded = True
if any_calls is not None:
self.calls = any_calls

for name, model_call in self.calls.items():
if name in self.__dict__:
self.__dict__[name].calls = model_call

loaded = True

return loaded

def _call_from_pycode(self):
"""
Helper function to import generated pycode.
``pycode`` is imported in the following sequence:
- a user-provided path from CLI
- ``~/.andes/pycode``
- ``<andes_root>/pycode``
"""
pycode = None

loaded = False

pycode = load_pycode_dot_andes()
# below are executed serially because of priority
pycode = reload_submodules('pycode')
if not pycode:
# or use `pycode` in the andes source folder
try:
pycode = importlib.import_module("andes.pycode")
logger.info("Loaded generated Python code in andes source dir.")
except ImportError:
pass
pycode_path = get_pycode_path(self.options.get("pycode_path"), mkdir=False)
pycode = load_pycode_from_path(pycode_path)
if not pycode:
pycode = reload_submodules('andes.pycode')
if not pycode:
pycode = load_pycode_from_path(os.path.join(andes_root(), 'pycode'))

# DO NOT USE `elif` here since below depends on the above.
if pycode:
self._expand_pycode(pycode)
loaded = True
try:
self._expand_pycode(pycode)
loaded = True
except KeyError:
logger.error("Your generated pycode is broken. Run `andes prep` to re-generate. ")

return loaded

Expand Down Expand Up @@ -1930,28 +1945,46 @@ def as_dict(self, vin=False, skip_empty=True):
return out


def load_pycode_dot_andes():
def load_pycode_from_path(pycode_path):
"""
Helper function to load pycode from ``.andes``.
"""
pycode = None

MODULE_PATH = get_dot_andes_path() + '/pycode/__init__.py'
MODULE_PATH = os.path.join(pycode_path, '__init__.py')
MODULE_NAME = 'pycode'

pycode = None
if os.path.isfile(MODULE_PATH):
try:
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
pycode = importlib.util.module_from_spec(spec) # NOQA
sys.modules[spec.name] = pycode
spec.loader.exec_module(pycode)
logger.info('Loaded generated Python code in "~/.andes/pycode".')
logger.info('Loaded generated Python code in "%s".', pycode_path)
except ImportError:
pass
logger.debug('Failed loading generated Python code in "%s".', pycode_path)

return pycode


def reload_submodules(module_name):
"""
Helper function for reloading an existing module and its submodules.
It is used to reload the ``pycode`` module after regenerating code.
"""

if module_name in sys.modules:
pycode = sys.modules[module_name]
for _, m in inspect.getmembers(pycode, inspect.ismodule):
importlib.reload(m)

logger.info('Reloaded generated Python code of module "%s".', module_name)
return pycode

return None


def _append_model_name(model_name, idx):
"""
Helper function for appending ``idx`` to model names.
Expand Down
16 changes: 14 additions & 2 deletions andes/utils/paths.py
Expand Up @@ -79,10 +79,22 @@ def displayable(self):
return ''.join(reversed(parts))


def andes_root():
"""
Return the root path to the andes source code.
"""

dir_name = os.path.dirname(os.path.abspath(__file__))
return os.path.normpath(os.path.join(dir_name, '..'))


def cases_root():
"""Return the root path to the stock cases"""
"""
Return the root path to the stock cases
"""

dir_name = os.path.dirname(os.path.abspath(__file__))
return os.path.join(dir_name, '..', 'cases')
return os.path.normpath(os.path.join(dir_name, '..', 'cases'))


def tests_root():
Expand Down
27 changes: 0 additions & 27 deletions docs/source/misc.rst
Expand Up @@ -76,33 +76,6 @@ if any input changes.
``EventFlag`` is a ``VarService`` that will be evaluated at each
iteration after discrete components and before equations.

Per Unit System
==============================

The bases for AC system are

- :math:`S_b^{ac}`: three-phase power in MVA. By default, :math:`S_b^{ac}=100 MVA` (in ``System.config.mva``).

- :math:`V_b^{ac}`: phase-to-phase voltage in kV.

- :math:`I_b^{ac}`: current base :math:`I_b^{ac} = \frac{S_b^{ac}} {\sqrt{3} V_b^{ac}}`

The bases for DC system are

- :math:`S_b^{dc}`: power in MVA. It is assumed to be the same as :math:`S_b^{ac}`.

- :math:`V_b^{dc}`: voltage in kV.

Some device parameters with specific properties are per unit values under the corresponding
device base ``Sn`` and ``Vn`` (if applicable).
These properties are documented in :py:mod:`andes.core.param.NumParam`.

After setting up the system, these parameters will be converted to the system base MVA
as specified in the config file (100 MVA by default).
The parameter values in the system base will be stored in the ``v`` attribute of the ``NumParam``,
and the original inputs in the device base will be stored to the ``vin`` attribute.
Values in the ``v`` attribute is what get utilized in computation.
Writing new values directly to ``vin`` will not affect the values in ``v`` afterwards.

Profiling Import
========================================
Expand Down
42 changes: 42 additions & 0 deletions docs/source/tutorial.rst
Expand Up @@ -1011,6 +1011,48 @@ The following parameter checks are applied after converting input values to arra
- Any ``inf`` will be replaced with :math:`10^{8}`, and ``-inf`` will be replaced with :math:`-10^{8}`.


Per Unit System
===============

The bases for AC system are

- :math:`S_b^{ac}`: three-phase power in MVA. By default, :math:`S_b^{ac}=100 MVA` (set by ``System.config.mva``).

- :math:`V_b^{ac}`: phase-to-phase voltage in kV.

- :math:`I_b^{ac}`: current base :math:`I_b^{ac} = \frac{S_b^{ac}} {\sqrt{3} V_b^{ac}}`

The bases for DC system are

- :math:`S_b^{dc}`: power in MVA. It is assumed to be the same as :math:`S_b^{ac}`.

- :math:`V_b^{dc}`: voltage in kV.

Some device parameters are given as per unit values under the device base power and voltage (if applicable).
For example, the Line model :py:mod:`andes.models.line.Line` has parameters ``r``, ``x`` and ``b``
as per unit values in the device bases ``Sn``, ``Vn1``, and ``Vn2``.
It is up to the user to check data consistency.
For example, line voltage bases are typically the same as bus nominal values.
If the ``r``, ``x`` and ``b`` are meant to be per unit values under the system base,
each Line device should use an ``Sn`` equal to the system base mva.

Parameters in device base will have a property value in the Model References page.
For example, ``Line.r`` has a property ``z``, which means it is a per unit impedance
in the device base.
To find out all applicable properties, refer to the "Other Parameters" section of
:py:mod:`andes.core.param.NumParam`.

After setting up the system, these parameters will be converted to per units
in the bases of system base MVA and bus nominal voltages.
The parameter values in the system base will be stored to the ``v`` attribute of the ``NumParam``.
The original inputs in the device base will be moved to the ``vin`` attribute.
For example, after setting up the system, ``Line.x.v`` is the line reactances in per unit
under system base.

Values in the ``v`` attribute is what get utilized in computation.
Writing new values directly to ``vin`` will not affect the values in ``v`` afterwards.
To alter parameters after setting up, refer to example notebook 2.

Cheatsheet
===========
A cheatsheet is available for quick lookup of supported commands.
Expand Down

0 comments on commit a89bfbd

Please sign in to comment.