####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program 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, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
"""This module implements classes to handle analysis output.
"""
####################################################################################################
import logging
import os
# import numpy as np
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
from PySpice.Unit.Unit import UnitValues
####################################################################################################
####################################################################################################
[docs]class Analysis:
    """Base class for the simulation output.
    Depending of the simulation type, the simulator will return waveforms as a function of
      * time
      * frequency
      * sweep
      * ...
    and corresponding to
      * a node's voltage
      * a source's current
      * ...
    The name of a waveform is
      * node's voltage: node's name
      * source's current: source'name
      * ...
    If the waveform name is a valid Python identifier, then you can get the corresponding waveform using::
      analysis.waveforme_name
    else you have to use this fallback::
      analysis['waveforme_name']
    Examples of usages::
        # Operating point analysis
        for node in analysis.nodes.values():
            print('Node {}: {:5.2f} V'.format(str(node), float(node)))
        for node in analysis.branches.values():
            print('Node {}: {:5.2f} A'.format(str(node), float(node)))
        # DC sensitivity analysis
        for element in analysis.elements.values():
            print(element, float(element))
        # Plot the voltage of the "out" node
        plt.plot(analysis.out.abscissa, analysis.out)
    Public Attributes:
      :attr:`nodes`
        Dictionary for node voltages indexed by node names
      :attr:`branches`
        Dictionary for branch currents indexed by source names
      :attr:`elements`
        Dictionary for elements ...
    """
    ##############################################
    def __init__(self, simulation, nodes=(), branches=(), elements=(), internal_parameters=()):
        # Fixme: branches are elements in fact, and elements is not yet supported ...
        self._simulation = simulation
        # Fixme: to func?
        self._nodes = {waveform.name:waveform for waveform in nodes}
        self._branches = {waveform.name:waveform for waveform in branches}
        self._elements = {waveform.name:waveform for waveform in elements}
        self._internal_parameters = {waveform.name:waveform for waveform in internal_parameters}
    ##############################################
    @property
    def simulation(self):
        """Return the simulation instance"""
        return self._simulation
    @property
    def nodes(self):
        return self._nodes
    @property
    def branches(self):
        return self._branches
    @property
    def elements(self):
        return self._elements
    @property
    def internal_parameters(self):
        return self._internal_parameters
   ##############################################
    def _get_item(self, name):
        # Fixme: cache dict ???
        if name in self._nodes:
            return self._nodes[name]
        elif name in self._branches:
            return self._branches[name]
        elif name in self._elements:
            return self._elements[name]
        elif name in self._internal_parameters:
            return self._internal_parameters[name]
        else:
            raise IndexError(name)
    ##############################################
    def __getitem__(self, name):
        try:
            return self._get_item(name)
        except IndexError:
            return self._get_item(name.lower())
    ##############################################
    @staticmethod
    def _format_dict(d):
        return os.linesep.join([' '*2 + str(x) for x in d])
    ##############################################
    def __getattr__(self, name):
        try:
            return self.__getitem__(name)
        except IndexError:
            raise AttributeError(name + os.linesep +
                                 'Nodes :' + os.linesep + self._format_dict(self._nodes) + os.linesep +
                                 'Branches :' + os.linesep + self._format_dict(self._branches) + os.linesep +
                                 'Elements :' + os.linesep + self._format_dict(self._elements) + os.linesep +
                                 'Internal Parameters :' + os.linesep + self._format_dict(self._internal_parameters)
            ) 
####################################################################################################
[docs]class OperatingPoint(Analysis):
    """This class implements an operating point analysis."""
    pass 
####################################################################################################
[docs]class SensitivityAnalysis(Analysis):
    """This class implements an sensitivity analysis."""
    ##############################################
    def __init__(self, simulation, elements, internal_parameters):
        super().__init__(simulation=simulation, elements=elements,
                         internal_parameters=internal_parameters) 
####################################################################################################
[docs]class DcAnalysis(Analysis):
    """This class implements a DC analysis.
    When the DC analysis is performed with multiple sources, sweep is the last source.
    The loop scheme is::
        for v1 in vsource1:
             for v2 in vsource2:
                 ...
    """
    ##############################################
    def __init__(self, simulation, sweep, nodes, branches, internal_parameters):
        super().__init__(simulation=simulation, nodes=nodes, branches=branches,
                         internal_parameters=internal_parameters)
        self._sweep = sweep
    ##############################################
    @property
    def sweep(self):
        """Return an Numpy array for the sweep abscissa"""
        return self._sweep 
####################################################################################################
[docs]class AcAnalysis(Analysis):
    """This class implements an AC analysis."""
    ##############################################
    def __init__(self, simulation, frequency, nodes, branches, internal_parameters):
        super().__init__(simulation=simulation, nodes=nodes, branches=branches,
                         internal_parameters=internal_parameters)
        self._frequency = frequency
    ##############################################
    @property
    def frequency(self):
        """Return an Numpy array for the frequency abscissa"""
        return self._frequency 
####################################################################################################
[docs]class TransientAnalysis(Analysis):
    """This class implements a transient analysis."""
    ##############################################
    def __init__(self, simulation, time, nodes, branches, internal_parameters):
        super().__init__(simulation=simulation, nodes=nodes, branches=branches,
                         internal_parameters=internal_parameters)
        self._time = time
    ##############################################
    @property
    def time(self):
        """Return an Numpy array for the time abscissa"""
        return self._time 
####################################################################################################
[docs]class PoleZeroAnalysis(Analysis):
    """This class implements a Pole-Zero analysis."""
    ##############################################
    def __init__(self, simulation, nodes, branches, internal_parameters):
        super().__init__(simulation=simulation, nodes=nodes, branches=branches,
                         internal_parameters=internal_parameters) 
####################################################################################################
[docs]class NoiseAnalysis(Analysis):
    """This class implements Noise analysis."""
    ##############################################
    def __init__(self, simulation, nodes, branches, internal_parameters):
        super().__init__(simulation=simulation, nodes=nodes, branches=branches,
                         internal_parameters=internal_parameters) 
####################################################################################################
[docs]class DistortionAnalysis(Analysis):
    """This class implements Distortion analysis."""
    ##############################################
    def __init__(self, simulation, frequency, nodes, branches, internal_parameters):
        super().__init__(simulation=simulation, nodes=nodes, branches=branches,
                         internal_parameters=internal_parameters)
        self._frequency = frequency
    ##############################################
    @property
    def frequency(self):
        """Return an Numpy array for the frequency abscissa"""
        return self._frequency 
####################################################################################################
[docs]class TransferFunctionAnalysis(Analysis):
    """This class implements Transfer Function (TF) analysis."""
    ##############################################
    def __init__(self, simulation, nodes, branches, internal_parameters):
        super().__init__(simulation=simulation, nodes=nodes, branches=branches,
                         internal_parameters=internal_parameters)