# -*- coding: iso-8859-1 -*-
# bfpss.py
# Brute-force PSS analysis module
# Copyright 2006 Giuseppe Venturini
# This file is part of the ahkab simulator.
#
# Ahkab is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 2 of the License.
#
# Ahkab is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License v2
# along with ahkab. If not, see <http://www.gnu.org/licenses/>.
"""Brute-force periodic steady state analysis module"""
from __future__ import (unicode_literals, absolute_import,
division, print_function)
import sys
import numpy as np
import numpy.linalg
import scipy
import scipy.sparse
from . import circuit
from . import dc_analysis
from . import components
from . import implicit_euler
from . import options
from . import printing
from . import results
from . import ticker
from . import transient
from . import utilities
[docs]def bfpss(circ, period, step=None, points=None, autonomous=False, x0=None,
mna=None, Tf=None, D=None, outfile='stdout',
vector_norm=lambda v: max(abs(v)), verbose=3):
"""Performs a PSS analysis employing the 'brute-force' algorithm
The time step is constant and IE will be used as DF.
**Parameters:**
circ : Circuit instance
the circuit to be simulated
period : float
the period of the solution
step : float, optional
the time step between consecutive points, it will be calculated
if not provided
points : int, optional
the number of points to be used to sample one period, it will be
calculated if not provided
autonomous : bool, optional
Is the circuit clocked or autonomously oscillating?
With the current implementation, setting ``autonomous=True``
will result in an exception being raised, autonomous circuits are
not supported
x0 : ndarray, optional
The initial guess to be used. (Experimental, needs work.)
mna, D, Tf : ndarrays, optional
The matrices describing the circuit may be supplied to speed up
the solution, if available. If not supplied, they will be
automatically calculated.
vector_norm : function, optional
The norm to be employed in the convergence checks.
Defaults to the Inf norm.
outfile : str, optional
the output filename. Defaults to ``'stdout'``.
verbose : int, optional
Verbosity level on a scale from 0 (silent) to 6 (very verbose).
The ``verbose`` flag is automatically set is to zero
if ``datafilename == 'stdout'``
.. note::
``step`` and ``points`` are mutually exclusive options:
* if ``step`` is specified, the number of points will be
automatically determined.
* if ``points`` is set, the step will be automatically determined.
* if none of them is set, ``options.bfpss_default_points`` will
be used as value for ``points`` and ``step`` computed accordingly.
**Returns:**
sol : results.pss_solution
The simulation results
"""
if outfile == "stdout":
verbose = 0
printing.print_info_line(
("Starting periodic steady state analysis:", 3), verbose)
printing.print_info_line(("Method: brute-force", 3), verbose)
if mna is None or Tf is None:
(mna, Tf) = dc_analysis.generate_mna_and_N(circ, verbose=verbose)
mna = utilities.remove_row_and_col(mna)
Tf = utilities.remove_row(Tf, rrow=0)
elif not mna.shape[0] == Tf.shape[0]:
printing.print_general_error(
"mna matrix and N vector have different number of rows.")
sys.exit(0)
if D is None:
D = transient.generate_D(circ, [mna.shape[0], mna.shape[0]])
D = utilities.remove_row_and_col(D)
elif not mna.shape == D.shape:
printing.print_general_error(
"mna matrix and D matrix have different sizes.")
raise ValueError
(points, step) = utilities.check_step_and_points(step, points, period,
options.bfpss_default_points)
n_of_var = mna.shape[0]
locked_nodes = circ.get_locked_nodes()
tick = ticker.ticker(increments_for_step=1)
sparse = n_of_var*points > options.dense_matrix_limit
CMAT = _build_CMAT(mna, D, step, points, tick, n_of_var=n_of_var,
sparse=sparse, verbose=verbose)
x = _build_x(mna, step, points, tick, x0=x0, n_of_var=n_of_var,
verbose=verbose)
Tf = _build_Tf(Tf, points, tick, n_of_var=n_of_var, verbose=verbose)
# time variable component: Tt this is always the same in each iter.
# So we build it once for all
# It holds all time-dependent sources (both V/I).
Tt = _build_Tt(circ, points, step, tick, n_of_var=n_of_var,
verbose=verbose)
# Indices to differentiate between currents and voltages in the
# convergence check
nv_indices = []
ni_indices = []
nv_1 = circ.get_nodes_number() - 1
ni = n_of_var - nv_1
for i in range(points):
nv_indices += (i * mna.shape[0] * np.ones(nv_1) + \
np.arange(nv_1)).tolist()
ni_indices += (i * mna.shape[0] * np.ones(ni) + \
np.arange(nv_1, n_of_var)).tolist()
converged = False
printing.print_info_line(("Solving... ", 3), verbose, print_nl=False)
tick.reset()
tick.display(verbose > 2)
if sparse:
J = scipy.sparse.lil_matrix(CMAT.shape)
else:
J = np.zeros(CMAT.shape)
T = np.zeros((CMAT.shape[0], 1))
# td is a np array that will hold the damping factors
td = np.zeros((points, 1))
iteration = 0 # newton iteration counter
while True:
if iteration: # the first time are already all zeros
if sparse:
J = scipy.sparse.lil_matrix(CMAT.shape)
else:
J[:, :] = 0
T[:, 0] = 0
td[:, 0] = 0
for index in range(1, points):
for elem in circ:
# build all dT(xn)/dxn (stored in J) and T(x)
if elem.is_nonlinear:
oports = elem.get_output_ports()
for opindex in range(len(oports)):
dports = elem.get_drive_ports(opindex)
v_ports = []
for dpindex in range(len(dports)):
dn1, dn2 = dports[dpindex]
# build v: remember we trashed the
# 0 row and 0 col of mna -> -1
v = 0
if dn1:
v = v + x[index * n_of_var + dn1 - 1, 0]
if dn2:
v = v - x[index * n_of_var + dn2 - 1, 0]
v_ports.append(v)
# all drive ports are ready.
n1, n2 = oports[opindex][0], oports[opindex][1]
if n1:
T[index * n_of_var + n1 - 1, 0] = T[index * n_of_var + n1 - 1, 0] + \
elem.i(opindex, v_ports)
if n2:
T[index * n_of_var + n2 - 1, 0] = T[index * n_of_var + n2 - 1, 0] - \
elem.i(opindex, v_ports)
for dpindex in range(len(dports)):
dn1, dn2 = dports[dpindex]
if n1:
if dn1:
J[index * n_of_var + n1 - 1, index * n_of_var + dn1 - 1] = \
J[index * n_of_var + n1 - 1, index * n_of_var + dn1 - 1] + \
elem.g(opindex, v_ports, dpindex)
if dn2:
J[index * n_of_var + n1 - 1, index * n_of_var + dn2 - 1] =\
J[index * n_of_var + n1 - 1, index * n_of_var + dn2 - 1] - 1.0 * \
elem.g(opindex, v_ports, dpindex)
if n2:
if dn1:
J[index * n_of_var + n2 - 1, index * n_of_var + dn1 - 1] = \
J[index * n_of_var + n2 - 1, index * n_of_var + dn1 - 1] - 1.0 * \
elem.g(opindex, v_ports, dpindex)
if dn2:
J[index * n_of_var + n2 - 1, index * n_of_var + dn2 - 1] =\
J[index * n_of_var + n2 - 1, index * n_of_var + dn2 - 1] + \
elem.g(opindex, v_ports, dpindex)
J = J + CMAT
residuo = CMAT*x + T + Tf + Tt
if sparse:
J = scipy.sparse.csc_matrix(J)
lu = scipy.sparse.linalg.splu(J)
dx = lu.solve(-residuo)
else:
dx = np.linalg.solve(J, -residuo)
# td
for index in range(points):
td[index, 0] = dc_analysis.get_td(dx[index*n_of_var:(index + 1)*n_of_var,
0].reshape(-1, 1),
locked_nodes, n=-1)
x = x + min(abs(td))[0] * dx
# convergence check
converged = _convergence_check(dx, x, nv_indices, ni_indices,
vector_norm)
if converged:
break
tick.step()
if options.bfpss_max_nr_iter and iteration == options.bfpss_max_nr_iter:
printing.print_general_error("Hit BFPSS_MAX_NR_ITER (" +
str(options.bfpss_max_nr_iter) +
"), iteration halted.")
converged = False
break
else:
iteration = iteration + 1
tick.hide(verbose > 2)
if converged:
printing.print_info_line(("done.", 3), verbose)
t = np.arange(points) * step
t = t.reshape((1, points))
x = x.reshape((points, n_of_var))
sol = results.pss_solution(circ=circ, method="brute-force",
period=period, outfile=outfile)
sol.set_results(t, x.T)
else:
print("failed.")
sol = None
return sol
def _convergence_check(dx, x, nv_indices, ni_indices, vector_norm):
"""Perform a convergence check using the specified vector norm"""
# sometimes something diverges... look out
if vector_norm(dx) is np.nan:
raise OverflowError
dxc = np.array(dx)
xc = np.array(x)
ret = (vector_norm(dxc[nv_indices]) < options.ver * \
vector_norm(xc[nv_indices]) + options.vea) and \
(not len(ni_indices) or \
vector_norm(dxc[ni_indices]) < options.ier * \
vector_norm(xc[ni_indices]) + options.iea)
return ret
def _build_CMAT(mna, D, step, points, tick, n_of_var=None, sparse=False, verbose=3):
if n_of_var is None:
n_of_var = mna.shape[0]
printing.print_info_line(("Building CMAT (%dx%d)... " %
(n_of_var*points, n_of_var*points), 5), verbose, print_nl=False)
tick.reset()
tick.display(verbose > 2)
C1, C0 = implicit_euler.get_df_coeff(step)
I = np.eye(n_of_var)
M = mna + C1*D
N = C0*D
if sparse:
CMAT = scipy.sparse.lil_matrix((n_of_var*points, n_of_var*points))
else:
CMAT = np.zeros((n_of_var*points, n_of_var*points))
for li in range(points): # li = line index
for ci in range(points):
if li == 0:
if ci == 0:
temp = I
elif ci == points - 1:
temp = -I
else:
continue # temp = Z
else:
if ci == li:
temp = M
elif ci == li - 1:
temp = N
else:
continue # temp = Z
CMAT = utilities.set_submatrix(row=li*n_of_var, col=ci*n_of_var,
dest_matrix=CMAT, source_matrix=temp)
tick.step()
tick.hide(verbose > 2)
if sparse:
CMAT = scipy.sparse.coo_matrix(CMAT)
printing.print_info_line(("done.", 5), verbose)
return CMAT
def _build_x(mna, step, points, tick, x0=None, n_of_var=None, verbose=3):
if n_of_var is None:
n_of_var = mna.shape[0]
printing.print_info_line(("Building x...", 5), verbose, print_nl=False)
tick.reset()
tick.display(verbose > 2)
x = np.zeros((points * n_of_var, 1))
if x0 is not None:
if isinstance(x0, results.op_solution):
x0 = x0.asarray()
if x0.shape[0] != n_of_var:
print("Warning x0 has the wrong dimensions. Using all 0s.")
else:
for index in range(points):
x = utilities.set_submatrix(row=index*n_of_var, col=0,
dest_matrix=x, source_matrix=x0)
tick.step()
tick.hide(verbose > 2)
printing.print_info_line(("done.", 5), verbose)
return x
def _build_Tf(sTf, points, tick, n_of_var, verbose=3):
printing.print_info_line(("Building Tf...", 5), verbose, print_nl=False)
tick.reset()
tick.display(verbose > 2)
Tf = np.zeros((points * n_of_var, 1))
for index in range(1, points):
Tf = utilities.set_submatrix(row=index*n_of_var, col=0, dest_matrix=Tf,
source_matrix=sTf)
tick.step()
tick.hide(verbose > 2)
printing.print_info_line(("done.", 5), verbose)
return Tf
def _build_Tt(circ, points, step, tick, n_of_var, verbose=3):
nv = circ.get_nodes_number()
printing.print_info_line(("Building Tt...", 5), verbose, print_nl=False)
tick.reset()
tick.display(verbose > 2)
Tt = np.zeros((points * n_of_var, 1))
for index in range(1, points):
v_eq = 0
time = index * step
for elem in circ:
if (isinstance(elem, components.sources.VSource) or isinstance(elem, components.sources.ISource)) and elem.is_timedependent:
if isinstance(elem, components.sources.VSource):
Tt[index * n_of_var + nv - 1 + v_eq, 0] = - \
1.0 * elem.V(time)
elif isinstance(elem, components.sources.ISource):
if elem.n1:
Tt[index * n_of_var + elem.n1 - 1, 0] = \
Tt[index * n_of_var + elem.n1 - 1, 0] + \
elem.I(time)
if elem.n2:
Tt[index * n_of_var + elem.n2 - 1, 0] = \
Tt[index * n_of_var + elem.n2 - 1, 0] - \
elem.I(time)
if circuit.is_elem_voltage_defined(elem):
v_eq = v_eq + 1
# print Tt[index*n_of_var:(index+1)*n_of_var]
tick.step()
tick.hide(verbose > 2)
printing.print_info_line(("done.", 5), verbose)
return Tt