Examples
========

One example is provided to demonstrate descriptive scheduling modeling.

DCOPF
----------


DC optimal power flow (DCOPF) is a fundamental routine used in power system analysis.
In this example, we demonstrate how to implement a DCOPF routine in a descriptive
manner using AMS. Below is a simplified DCOPF code snippet.
The full code can be found in :py:mod:`ams.routines.dcopf.DCOPF`.

Data Section
^^^^^^^^^^^^

.. code-block:: python
   :linenos:

      class DCOPF(RoutineBase):

          def __init__(self, system, config):
              RoutineBase.__init__(self, system, config)
              self.info = 'DC Optimal Power Flow'
              self.type = 'DCED'
              # --- Data Section ---
              # --- generator cost ---
              self.c1 = RParam(info='Gen cost coefficient 1',
                               name='c1', tex_name=r'c_{1}', unit=r'$/(p.u.)', 
                               model='GCost', src='c1',
                               indexer='gen', imodel='StaticGen',)
              # --- generator ---
              self.ug = RParam(info='Gen connection status',
                               name='ug', tex_name=r'u_{g}',
                               model='StaticGen', src='u',
                               no_parse=True)
              self.ctrl = RParam(info='Gen controllability',
                                 name='ctrl', tex_name=r'c_{trl}',
                                 model='StaticGen', src='ctrl',
                                 no_parse=True)
              self.ctrle = NumOpDual(info='Effective Gen controllability',
                                     name='ctrle', tex_name=r'c_{trl, e}',
                                     u=self.ctrl, u2=self.ug,
                                     fun=np.multiply, no_parse=True)
              # --- load ---
              self.pd = RParam(info='active demand',
                               name='pd', tex_name=r'p_{d}',
                               model='StaticLoad', src='p0',
                               unit='p.u.',)
              # --- line ---
              self.rate_a = RParam(info='long-term flow limit',
                                   name='rate_a', tex_name=r'R_{ATEA}',
                                   unit='MVA', model='Line',)
              # --- line angle difference ---
              self.amax = NumOp(u=self.rate_a, fun=np.ones_like,
                                rfun=np.dot, rargs=dict(b=np.pi),
                                name='amax', tex_name=r'\theta_{max}',
                                info='max line angle difference',
                                no_parse=True,)
              # --- connection matrix ---
              self.Cg = RParam(info='Gen connection matrix',
                               name='Cg', tex_name=r'C_{g}',
                               model='mats', src='Cg',
                               no_parse=True, sparse=True,)
              # --- system matrix ---
              self.Bbus = RParam(info='Bus admittance matrix',
                                 name='Bbus', tex_name=r'B_{bus}',
                                 model='mats', src='Bbus',
                                 no_parse=True, sparse=True,)

Lines 1-4: Derive subclass ``DCOPF`` from ``RoutineBase``.

Lines 5-6: Define routine information and type.

Lines 9-12: Define linear generator cost coefficients ``c1``, where it
sources from ``GCost.c1`` and sorted by ``StaticGen.gen``.

Lines 22-24: Define effective controllability as service ``ctrle``,
where it multiplies ``ctrl`` and ``ug``.

Lines 42-45: Define generator connection matrix ``Cg``, where it sources from
matrix processor ``mats.Cg`` and is sparse.

Model Section
^^^^^^^^^^^^^

.. code-block:: python
   :linenos:
   :lineno-start: 51

              # --- Model Section ---
              # --- generation ---
              self.pg = Var(info='Gen active power',
                            unit='p.u.',
                            name='pg', tex_name=r'p_g',
                            model='StaticGen', src='p',
                            v0=self.pg0)
              # --- bus ---
              self.aBus = Var(info='Bus voltage angle',
                              unit='rad',
                              name='aBus', tex_name=r'\theta_{bus}',
                              model='Bus', src='a',)
              # --- power balance ---
              pb = 'Bbus@aBus + Pbusinj + Cl@pd + Csh@gsh - Cg@pg'
              self.pb = Constraint(name='pb', info='power balance',
                                   e_str=pb, is_eq=True,)
              # --- line flow ---
              self.plf = Var(info='Line flow',
                             unit='p.u.',
                             name='plf', tex_name=r'p_{lf}',
                             model='Line',)
              self.plflb = Constraint(info='line flow lower bound',
                                      name='plflb', type='uq',
                                      e_str='-Bf@aBus - Pfinj - rate_a',)
              self.plfub = Constraint(info='line flow upper bound',
                                      name='plfub', type='uq',
                                      e_str='Bf@aBus + Pfinj - rate_a',)
              # --- objective ---
              obj = 'sum(mul(c2, power(pg, 2)))'
              obj += '+ sum(mul(c1, pg))'
              obj += '+ sum(mul(ug, c0))'
              self.obj = Objective(name='obj',
                                   info='total cost', unit='$',
                                   sense='min', e_str=obj,)

Continued from the above code.

Lines 53-57: Define variable ``pg``, where it links to ``StaticGen.p``
and initial value ``pg0``.

Lines 68-71: Define variable ``plf``, where it links to ``Line`` with
no target source variable nor initial value.

Lines 72-77: Define inequality constraints ``plflb`` and ``plfub``
for line flow limits.

Lines 79-84: Define objective function ``obj`` for minimizing total cost.

Finalize
^^^^^^^^

Lastly, similar to finalize a device model, we need to finalize the routine
by adding the ``RTED`` to the routine list in ``$HOME/ams/ams/routines/__init__.py``,
where 'rted' is the file name, and 'RTED' is the routine name.

.. code-block:: python

      all_routines = OrderedDict([
            ... ...
            ('dcopf', ['DCOPF']),
            ('ed', ['ED', 'EDDG', 'EDES']),
            ('rted', ['RTED', 'RTEDDG', 'RTEDES', 'RTEDVIS']),
            ... ...
      ])

.. note::
      Refer to the documentation "Example - Customize Formulation"
      for API customization that does not require modification of the source code.