from collections import OrderedDict
from collections.abc import Iterable
from copy import deepcopy
from math import cos, sin, pi
from numbers import Real
from xml.etree import ElementTree as ET
import sys
import warnings

import numpy as np

import openmc
import openmc.checkvalue as cv
from openmc.surface import Halfspace
from openmc.region import Region, Intersection, Complement
from openmc._xml import get_text
from .mixin import IDManagerMixin

class Cell(IDManagerMixin):
    r"""A region of space defined as the intersection of half-space created by
    quadric surfaces.

    Parameters
    ----------
    cell_id : int, optional
        Unique identifier for the cell. If not specified, an identifier will
        automatically be assigned.
    name : str, optional
        Name of the cell. If not specified, the name is the empty string.
    fill : openmc.Material or openmc.Universe or openmc.Lattice or None or iterable of openmc.Material, optional
        Indicates what the region of space is filled with
    region : openmc.Region, optional
        Region of space that is assigned to the cell.

    Attributes
    ----------
    id : int
        Unique identifier for the cell
    name : str
        Name of the cell
    fill : openmc.Material or openmc.Universe or openmc.Lattice or None or iterable of openmc.Material
        Indicates what the region of space is filled with. If None, the cell is
        treated as a void. An iterable of materials is used to fill repeated
        instances of a cell with different materials.
    fill_type : {'material', 'universe', 'lattice', 'distribmat', 'void'}
        Indicates what the cell is filled with.
    region : openmc.Region or None
        Region of space that is assigned to the cell.
    rotation : Iterable of float
        If the cell is filled with a universe, this array specifies the angles
        in degrees about the x, y, and z axes that the filled universe should be
        rotated. The rotation applied is an intrinsic rotation with specified
        Tait-Bryan angles. That is to say, if the angles are :math:`(\phi,
        \theta, \psi)`, then the rotation matrix applied is :math:`R_z(\psi)
        R_y(\theta) R_x(\phi)` or

        .. math::

           \left [ \begin{array}{ccc} \cos\theta \cos\psi & -\cos\phi \sin\psi
           + \sin\phi \sin\theta \cos\psi & \sin\phi \sin\psi + \cos\phi
           \sin\theta \cos\psi \\ \cos\theta \sin\psi & \cos\phi \cos\psi +
           \sin\phi \sin\theta \sin\psi & -\sin\phi \cos\psi + \cos\phi
           \sin\theta \sin\psi \\ -\sin\theta & \sin\phi \cos\theta & \cos\phi
           \cos\theta \end{array} \right ]

        A rotation matrix can also be specified directly by setting this
        attribute to a nested list (or 2D numpy array) that specifies each
        element of the matrix.
    rotation_matrix : numpy.ndarray
        The rotation matrix defined by the angles specified in the
        :attr:`Cell.rotation` property.
    temperature : float or iterable of float
        Temperature of the cell in Kelvin.  Multiple temperatures can be given
        to give each distributed cell instance a unique temperature.
    translation : Iterable of float
        If the cell is filled with a universe, this array specifies a vector
        that is used to translate (shift) the universe.
    paths : list of str
        The paths traversed through the CSG tree to reach each cell
        instance. This property is initialized by calling the
        :meth:`Geometry.determine_paths` method.
    num_instances : int
        The number of instances of this cell throughout the geometry.
    volume : float
        Volume of the cell in cm^3. This can either be set manually or
        calculated in a stochastic volume calculation and added via the
        :meth:`Cell.add_volume_information` method.

    """

    next_id = 1
    used_ids = set()

    def __init__(self, cell_id=None, name='', fill=None, region=None):
        # Initialize Cell class attributes
        self.id = cell_id
        self.name = name
        self.fill = fill
        self.region = region
        self._rotation = None
        self._rotation_matrix = None
        self._temperature = None
        self._translation = None
        self._paths = None
        self._num_instances = None
        self._volume = None
        self._atoms = None

    def __contains__(self, point):
        if self.region is None:
            return True
        else:
            return point in self.region

    def __repr__(self):
        string = 'Cell\n'
        string += '{: <16}=\t{}\n'.format('\tID', self.id)
        string += '{: <16}=\t{}\n'.format('\tName', self.name)

        if self.fill_type == 'material':
            string += '{: <16}=\tMaterial {}\n'.format('\tFill', self.fill.id)
        elif self.fill_type == 'void':
            string += '{: <16}=\tNone\n'.format('\tFill')
        elif self.fill_type == 'distribmat':
            string += '{: <16}=\t{}\n'.format('\tFill', list(map(
                lambda m: m if m is None else m.id, self.fill)))
        else:
            string += '{: <16}=\t{}\n'.format('\tFill', self.fill.id)

        string += '{: <16}=\t{}\n'.format('\tRegion', self.region)
        string += '{: <16}=\t{}\n'.format('\tRotation', self.rotation)
        if self.fill_type == 'material':
            string += '\t{0: <15}=\t{1}\n'.format('Temperature',
                                                  self.temperature)
        string += '{: <16}=\t{}\n'.format('\tTranslation', self.translation)

        return string

    @property
    def name(self):
        return self._name

    @property
    def fill(self):
        return self._fill

    @property
    def fill_type(self):
        if isinstance(self.fill, openmc.Material):
            return 'material'
        elif isinstance(self.fill, openmc.Universe):
            return 'universe'
        elif isinstance(self.fill, openmc.Lattice):
            return 'lattice'
        elif isinstance(self.fill, Iterable):
            return 'distribmat'
        else:
            return 'void'

    @property
    def region(self):
        return self._region

    @property
    def rotation(self):
        return self._rotation

    @property
    def rotation_matrix(self):
        return self._rotation_matrix

    @property
    def temperature(self):
        return self._temperature

    @property
    def translation(self):
        return self._translation

    @property
    def volume(self):
        return self._volume

    @property
    def paths(self):
        if self._paths is None:
            raise ValueError('Cell instance paths have not been determined. '
                             'Call the Geometry.determine_paths() method.')
        return self._paths

    @property
    def bounding_box(self):
        if self.region is not None:
            return self.region.bounding_box
        else:
            return (np.array([-np.inf, -np.inf, -np.inf]),
                    np.array([np.inf, np.inf, np.inf]))

    @property
    def num_instances(self):
        if self._num_instances is None:
            raise ValueError(
                'Number of cell instances have not been determined. Call the '
                'Geometry.determine_paths() method.')
        return self._num_instances

    @name.setter
    def name(self, name):
        if name is not None:
            cv.check_type('cell name', name, str)
            self._name = name
        else:
            self._name = ''

    @fill.setter
    def fill(self, fill):
        if fill is not None:
            if isinstance(fill, Iterable):
                for i, f in enumerate(fill):
                    if f is not None:
                        cv.check_type('cell.fill[i]', f, openmc.Material)

            elif not isinstance(fill, (openmc.Material, openmc.Lattice,
                                       openmc.Universe)):
                msg = 'Unable to set Cell ID="{0}" to use a non-Material or ' \
                      'Universe fill "{1}"'.format(self._id, fill)
                raise ValueError(msg)

        self._fill = fill

    @rotation.setter
    def rotation(self, rotation):
        cv.check_length('cell rotation', rotation, 3)
        self._rotation = np.asarray(rotation)

        # Save rotation matrix -- the reason we do this instead of having it be
        # automatically calculated when the rotation_matrix property is accessed
        # is so that plotting on a rotated geometry can be done faster.
        if self._rotation.ndim == 2:
            # User specified rotation matrix directly
            self._rotation_matrix = self._rotation
        else:
            phi, theta, psi = self.rotation*(-pi/180.)
            c3, s3 = cos(phi), sin(phi)
            c2, s2 = cos(theta), sin(theta)
            c1, s1 = cos(psi), sin(psi)
            self._rotation_matrix = np.array([
                [c1*c2, c1*s2*s3 - c3*s1, s1*s3 + c1*c3*s2],
                [c2*s1, c1*c3 + s1*s2*s3, c3*s1*s2 - c1*s3],
                [-s2, c2*s3, c2*c3]])

    @translation.setter
    def translation(self, translation):
        cv.check_type('cell translation', translation, Iterable, Real)
        cv.check_length('cell translation', translation, 3)
        self._translation = np.asarray(translation)

    @temperature.setter
    def temperature(self, temperature):
        # Make sure temperatures are positive
        cv.check_type('cell temperature', temperature, (Iterable, Real))
        if isinstance(temperature, Iterable):
            cv.check_type('cell temperature', temperature, Iterable, Real)
            for T in temperature:
                cv.check_greater_than('cell temperature', T, 0.0, True)
        else:
            cv.check_greater_than('cell temperature', temperature, 0.0, True)

        # If this cell is filled with a universe or lattice, propagate
        # temperatures to all cells contained. Otherwise, simply assign it.
        if self.fill_type in ('universe', 'lattice'):
            for c in self.get_all_cells().values():
                if c.fill_type == 'material':
                    c._temperature = temperature
        else:
            self._temperature = temperature

    @region.setter
    def region(self, region):
        if region is not None:
            cv.check_type('cell region', region, Region)
        self._region = region

    @volume.setter
    def volume(self, volume):
        if volume is not None:
            cv.check_type('cell volume', volume, Real)
        self._volume = volume

    def add_volume_information(self, volume_calc):
        """Add volume information to a cell.

        Parameters
        ----------
        volume_calc : openmc.VolumeCalculation
            Results from a stochastic volume calculation

        """
        if volume_calc.domain_type == 'cell':
            if self.id in volume_calc.volumes:
                self._volume = volume_calc.volumes[self.id].n
                self._atoms = volume_calc.atoms[self.id]
            else:
                raise ValueError('No volume information found for this cell.')
        else:
            raise ValueError('No volume information found for this cell.')

    def get_nuclides(self):
        """Returns all nuclides in the cell

        Returns
        -------
        nuclides : list of str
            List of nuclide names

        """
        return self.fill.get_nuclides() if self.fill_type != 'void' else []

    def get_nuclide_densities(self):
        """Return all nuclides contained in the cell and their densities

        Returns
        -------
        nuclides : collections.OrderedDict
            Dictionary whose keys are nuclide names and values are 2-tuples of
            (nuclide, density)

        """

        nuclides = OrderedDict()

        if self.fill_type == 'material':
            nuclides.update(self.fill.get_nuclide_densities())
        elif self.fill_type == 'void':
            pass
        else:
            if self._atoms is not None:
                volume = self.volume
                for name, atoms in self._atoms.items():
                    nuclide = openmc.Nuclide(name)
                    density = 1.0e-24 * atoms.n/volume  # density in atoms/b-cm
                    nuclides[name] = (nuclide, density)
            else:
                raise RuntimeError(
                    'Volume information is needed to calculate microscopic cross '
                    'sections for cell {}. This can be done by running a '
                    'stochastic volume calculation via the '
                    'openmc.VolumeCalculation object'.format(self.id))

        return nuclides

    def get_all_cells(self, memo=None):
        """Return all cells that are contained within this one if it is filled with a
        universe or lattice

        Returns
        -------
        cells : collections.orderedDict
            Dictionary whose keys are cell IDs and values are :class:`Cell`
            instances

        """

        cells = OrderedDict()

        if memo and self in memo:
            return cells

        if memo is not None:
            memo.add(self)

        if self.fill_type in ('universe', 'lattice'):
            cells.update(self.fill.get_all_cells(memo))

        return cells

    def get_all_materials(self, memo=None):
        """Return all materials that are contained within the cell

        Returns
        -------
        materials : collections.OrderedDict
            Dictionary whose keys are material IDs and values are
            :class:`Material` instances

        """
        materials = OrderedDict()
        if self.fill_type == 'material':
            materials[self.fill.id] = self.fill
        elif self.fill_type == 'distribmat':
            for m in self.fill:
                if m is not None:
                    materials[m.id] = m
        else:
            # Append all Cells in each Cell in the Universe to the dictionary
            cells = self.get_all_cells(memo)
            for cell in cells.values():
                materials.update(cell.get_all_materials(memo))

        return materials

    def get_all_universes(self):
        """Return all universes that are contained within this one if any of
        its cells are filled with a universe or lattice.

        Returns
        -------
        universes : collections.OrderedDict
            Dictionary whose keys are universe IDs and values are
            :class:`Universe` instances

        """

        universes = OrderedDict()

        if self.fill_type == 'universe':
            universes[self.fill.id] = self.fill
            universes.update(self.fill.get_all_universes())
        elif self.fill_type == 'lattice':
            universes.update(self.fill.get_all_universes())

        return universes

    def clone(self, clone_materials=True, clone_regions=True,  memo=None):
        """Create a copy of this cell with a new unique ID, and clones
        the cell's region and fill.

        Parameters
        ----------
        clone_materials : bool
            Whether to create separate copies of the materials filling cells
            contained in this cell, or the material filling this cell.
        clone_regions : bool
            Whether to create separate copies of the regions bounding cells
            contained in this cell, and the region bounding this cell.
        memo : dict or None
            A nested dictionary of previously cloned objects. This parameter
            is used internally and should not be specified by the user.

        Returns
        -------
        clone : openmc.Cell
            The clone of this cell

        """

        if memo is None:
            memo = {}

        # If no memoize'd clone exists, instantiate one
        if self not in memo:
            # Temporarily remove paths
            paths = self._paths
            self._paths = None

            clone = deepcopy(self)
            clone.id = None
            clone._num_instances = None

            # Restore paths on original instance
            self._paths = paths

            if self.region is not None:
                if clone_regions:
                    clone.region = self.region.clone(memo)
                else:
                    clone.region = self.region
            if self.fill is not None:
                if self.fill_type == 'distribmat':
                    if not clone_materials:
                        clone.fill = self.fill
                    else:
                        clone.fill = [fill.clone(memo) if fill is not None else
                                      None for fill in self.fill]
                elif self.fill_type == 'material':
                    if not clone_materials:
                        clone.fill = self.fill
                    else:
                        clone.fill = self.fill.clone(memo)
                else:
                    clone.fill = self.fill.clone(clone_materials,
                         clone_regions, memo)

            # Memoize the clone
            memo[self] = clone

        return memo[self]

    def create_xml_subelement(self, xml_element, memo=None):
        """Add the cell's xml representation to an incoming xml element

        Parameters
        ----------
        xml_element : xml.etree.ElementTree.Element
            XML element to be added to

        memo : set or None
            A set of object IDs representing geometry entities already
            written to ``xml_element``. This parameter is used internally
            and should not be specified by users.

        Returns
        -------
        None

        """
        element = ET.Element("cell")
        element.set("id", str(self.id))

        if len(self._name) > 0:
            element.set("name", str(self.name))

        if self.fill_type == 'void':
            element.set("material", "void")

        elif self.fill_type == 'material':
            element.set("material", str(self.fill.id))

        elif self.fill_type == 'distribmat':
            element.set("material", ' '.join(['void' if m is None else str(m.id)
                                              for m in self.fill]))

        elif self.fill_type in ('universe', 'lattice'):
            element.set("fill", str(self.fill.id))
            self.fill.create_xml_subelement(xml_element, memo)

        if self.region is not None:
            # Set the region attribute with the region specification
            region = str(self.region)
            if region.startswith('('):
                region = region[1:-1]
            if len(region) > 0:
                element.set("region", region)

            # Only surfaces that appear in a region are added to the geometry
            # file, so the appropriate check is performed here. First we create
            # a function which is called recursively to navigate through the CSG
            # tree. When it reaches a leaf (a Halfspace), it creates a <surface>
            # element for the corresponding surface if none has been created
            # thus far.
            def create_surface_elements(node, element, memo=None):
                if isinstance(node, Halfspace):
                    if memo and node.surface in memo:
                        return
                    if memo is not None:
                        memo.add(node.surface)
                    xml_element.append(node.surface.to_xml_element())

                elif isinstance(node, Complement):
                    create_surface_elements(node.node, element, memo)
                else:
                    for subnode in node:
                        create_surface_elements(subnode, element, memo)

            # Call the recursive function from the top node
            create_surface_elements(self.region, xml_element, memo)

        if self.temperature is not None:
            if isinstance(self.temperature, Iterable):
                element.set("temperature", ' '.join(
                    str(t) for t in self.temperature))
            else:
                element.set("temperature", str(self.temperature))

        if self.translation is not None:
            element.set("translation", ' '.join(map(str, self.translation)))

        if self.rotation is not None:
            element.set("rotation", ' '.join(map(str, self.rotation.ravel())))

        return element

    @classmethod
    def from_xml_element(cls, elem, surfaces, materials, get_universe):
        """Generate cell from XML element

        Parameters
        ----------
        elem : xml.etree.ElementTree.Element
            `<cell>` element
        surfaces : dict
            Dictionary mapping surface IDs to :class:`openmc.Surface` instances
        materials : dict
            Dictionary mapping material IDs to :class:`openmc.Material`
            instances (defined in :math:`openmc.Geometry.from_xml`)
        get_universe : function
            Function returning universe (defined in
            :meth:`openmc.Geometry.from_xml`)

        Returns
        -------
        Cell
            Cell instance

        """
        cell_id = int(get_text(elem, 'id'))
        name = get_text(elem, 'name')
        c = cls(cell_id, name)

        # Assign material/distributed materials or fill
        mat_text = get_text(elem, 'material')
        if mat_text is not None:
            mat_ids = mat_text.split()
            if len(mat_ids) > 1:
                c.fill = [materials[i] for i in mat_ids]
            else:
                c.fill = materials[mat_ids[0]]
        else:
            fill_id = int(get_text(elem, 'fill'))
            c.fill = get_universe(fill_id)

        # Assign region
        region = get_text(elem, 'region')
        if region is not None:
            c.region = Region.from_expression(region, surfaces)

        # Check for other attributes
        t = get_text(elem, 'temperature')
        if t is not None:
            if ' ' in t:
                c.temperature = [float(t_i) for t_i in t.split()]
            else:
                c.temperature = float(t)
        for key in ('temperature', 'rotation', 'translation'):
            value = get_text(elem, key)
            if value is not None:
                setattr(c, key, [float(x) for x in value.split()])

        # Add this cell to appropriate universe
        univ_id = int(get_text(elem, 'universe', 0))
        get_universe(univ_id).add_cell(c)
        return c


"""
python implementation of shadow3 OE object
"""
import numpy
from shadow4.compatibility.gfile import GFile

class OE(object):

    def __init__(self):

        self.FMIRR = 5
        self.F_TORUS = 0
        self.FCYL = 0
        self.F_EXT = 0
        self.FSTAT = 0
        self.F_SCREEN = 0
        self.F_PLATE = 0
        self.FSLIT = 0
        self.FWRITE = 0
        self.F_RIPPLE = 0
        self.F_MOVE = 0
        self.F_THICK = 0
        self.F_BRAGG_A = 0
        self.F_G_S = 0
        self.F_R_RAN = 0
        self.F_GRATING = 0
        self.F_MOSAIC = 0
        self.F_JOHANSSON = 0
        self.F_SIDE = 0
        self.F_CENTRAL = 0
        self.F_CONVEX = 0
        self.F_REFLEC = 0
        self.F_RUL_ABS = 0
        self.F_RULING = 0
        self.F_PW = 0
        self.F_PW_C = 0
        self.F_VIRTUAL = 0
        self.FSHAPE = 1
        self.FHIT_C = 0
        self.F_MONO = 0
        self.F_REFRAC = 0
        self.F_DEFAULT = 1
        self.F_REFL = 0
        self.F_HUNT = 1
        self.F_CRYSTAL = 0
        self.F_PHOT_CENT = 0
        self.F_ROUGHNESS = 0
        self.F_ANGLE = 0
        self.NPOINT = 5000
        self.NCOL = 18
        self.N_SCREEN = 0
        self.ISTAR1 = 0
        self.CIL_ANG = 0.00000000000000
        self.ELL_THE = 0.00000000000000
        self.N_PLATES = 0
        self.IG_SEED = 0
        self.MOSAIC_SEED = 1626261131
        self.ALPHA = 0.00000000000000
        self.SSOUR = 0.00000000000000
        self.THETA = 0.00000000000000
        self.SIMAG = 0.00000000000000
        self.RDSOUR = 0.00000000000000
        self.RTHETA = 0.00000000000000
        self.OFF_SOUX = 0.00000000000000
        self.OFF_SOUY = 0.00000000000000
        self.OFF_SOUZ = 0.00000000000000
        self.ALPHA_S = 0.00000000000000
        self.RLEN1 = 0.00000000000000
        self.RLEN2 = 0.00000000000000
        self.RMIRR = 0.00000000000000
        self.AXMAJ = 0.00000000000000
        self.AXMIN = 0.00000000000000
        self.CONE_A = 0.00000000000000
        self.R_MAJ = 0.00000000000000
        self.R_MIN = 0.00000000000000
        self.RWIDX1 = 0.00000000000000
        self.RWIDX2 = 0.00000000000000
        self.PARAM = 0.00000000000000
        self.HUNT_H = 0.00000000000000
        self.HUNT_L = 0.00000000000000
        self.BLAZE = 0.00000000000000
        self.RULING = 12000.0000000000
        self.ORDER = -1.00000000000000
        self.PHOT_CENT = 5.00000000000000
        self.X_ROT = 0.00000000000000
        self.D_SPACING = 0.00000000000000
        self.A_BRAGG = 0.00000000000000
        self.SPREAD_MOS = 0.00000000000000
        self.THICKNESS = 0.00000000000000
        self.R_JOHANSSON = 0.00000000000000
        self.Y_ROT = 0.00000000000000
        self.Z_ROT = 0.00000000000000
        self.OFFX = 0.00000000000000
        self.OFFY = 0.00000000000000
        self.OFFZ = 0.00000000000000
        self.SLLEN = 0.00000000000000
        self.SLWID = 0.00000000000000
        self.SLTILT = 0.00000000000000
        self.COD_LEN = 0.00000000000000
        self.COD_WID = 0.00000000000000
        self.X_SOUR = 0.00000000000000
        self.Y_SOUR = 0.00000000000000
        self.Z_SOUR = 0.00000000000000
        self.X_SOUR_ROT = 0.00000000000000
        self.Y_SOUR_ROT = 0.00000000000000
        self.Z_SOUR_ROT = 0.00000000000000
        self.R_LAMBDA = 5.00000000000000
        self.THETA_I = 0.00000000000000
        self.ALPHA_I = 0.00000000000000
        self.T_INCIDENCE = 88.0000000000000
        self.T_SOURCE = 10.0000000000000
        self.T_IMAGE = 20.0000000000000
        self.T_REFLECTION = 88.0000000000000
        self.FILE_SOURCE = "begin.dat"
        self.FILE_RIP = ""
        self.FILE_REFL = ""
        self.FILE_MIR = ""
        self.FILE_ROUGH = ""
        self.FZP = 0
        self.HOLO_R1 = 300.000000000000
        self.HOLO_R2 = 300.000000000000
        self.HOLO_DEL = -20.0000000000000
        self.HOLO_GAM = -20.0000000000000
        self.HOLO_W = 4879.86000000000
        self.HOLO_RT1 = 0.00000000000000
        self.HOLO_RT2 = 0.00000000000000
        self.AZIM_FAN = 0.00000000000000
        self.DIST_FAN = 0.00000000000000
        self.COMA_FAC = 0.00000000000000
        self.ALFA = 0.00000000000000
        self.GAMMA = 0.00000000000000
        self.R_IND_OBJ = 1.00000000000000
        self.R_IND_IMA = 1.00000000000000
        self.R_ATTENUATION_OBJ = 0.00000000000000
        self.R_ATTENUATION_IMA = 0.00000000000000
        self.F_R_IND = 0
        self.FILE_R_IND_OBJ = ""
        self.FILE_R_IND_IMA = ""
        self.RUL_A1 = 0.00000000000000
        self.RUL_A2 = 0.00000000000000
        self.RUL_A3 = 0.00000000000000
        self.RUL_A4 = 0.00000000000000
        self.F_POLSEL = 4
        self.F_FACET = 0
        self.F_FAC_ORIENT = 0
        self.F_FAC_LATT = 0
        self.RFAC_LENX = 10.0000000000000
        self.RFAC_LENY = 10.0000000000000
        self.RFAC_PHAX = 0.00000000000000
        self.RFAC_PHAY = 0.00000000000000
        self.RFAC_DELX1 = 0.00000000000000
        self.RFAC_DELX2 = 0.00000000000000
        self.RFAC_DELY1 = 0.00000000000000
        self.RFAC_DELY2 = 0.00000000000000
        self.FILE_FAC = ""
        self.F_SEGMENT = 0
        self.ISEG_XNUM = 1
        self.ISEG_YNUM = 1
        self.FILE_SEGMENT = ""
        self.FILE_SEGP = ""
        self.SEG_LENX = 0.00000000000000
        self.SEG_LENY = 0.00000000000000
        self.F_KOMA = 0
        self.FILE_KOMA = ""
        self.F_EXIT_SHAPE = 0
        self.F_INC_MNOR_ANG = 0
        self.ZKO_LENGTH = 0.00000000000000
        self.RKOMA_CX = 0.00000000000000
        self.RKOMA_CY = 0.00000000000000
        self.F_KOMA_CA = 0
        self.FILE_KOMA_CA = ""
        self.F_KOMA_BOUNCE = 0
        self.X_RIP_AMP = 0.00000000000000
        self.X_RIP_WAV = 0.00000000000000
        self.X_PHASE = 0.00000000000000
        self.Y_RIP_AMP = 0.00000000000000
        self.Y_RIP_WAV = 0.00000000000000
        self.Y_PHASE = 0.00000000000000
        self.N_RIP = 0
        self.ROUGH_X = 0.00000000000000
        self.ROUGH_Y = 0.00000000000000
        self.OE_NUMBER = 0
        self.IDUMMY = 0
        self.DUMMY = 0.00000000000000

        self.CX_SLIT = numpy.zeros(11)
        self.CX_SLIT[1] = 0.00000000000000
        self.CX_SLIT[2] = 0.00000000000000
        self.CX_SLIT[3] = 0.00000000000000
        self.CX_SLIT[4] = 0.00000000000000
        self.CX_SLIT[5] = 0.00000000000000
        self.CX_SLIT[6] = 0.00000000000000
        self.CX_SLIT[7] = 0.00000000000000
        self.CX_SLIT[8] = 0.00000000000000
        self.CX_SLIT[9] = 0.00000000000000
        self.CX_SLIT[10] = 0.00000000000000

        self.CZ_SLIT = numpy.zeros(11)
        self.CZ_SLIT[1] = 0.00000000000000
        self.CZ_SLIT[2] = 0.00000000000000
        self.CZ_SLIT[3] = 0.00000000000000
        self.CZ_SLIT[4] = 0.00000000000000
        self.CZ_SLIT[5] = 0.00000000000000
        self.CZ_SLIT[6] = 0.00000000000000
        self.CZ_SLIT[7] = 0.00000000000000
        self.CZ_SLIT[8] = 0.00000000000000
        self.CZ_SLIT[9] = 0.00000000000000
        self.CZ_SLIT[10] = 0.00000000000000

        self.D_PLATE = numpy.zeros(11)
        self.D_PLATE[1] = 0.00000000000000
        self.D_PLATE[2] = 0.00000000000000
        self.D_PLATE[3] = 0.00000000000000
        self.D_PLATE[4] = 0.00000000000000
        self.D_PLATE[5] = 0.00000000000000
        self.D_PLATE[6] = 0.00000000000000
        self.D_PLATE[7] = 0.00000000000000
        self.D_PLATE[8] = 0.00000000000000
        self.D_PLATE[9] = 0.00000000000000
        self.D_PLATE[10] = 0.00000000000000

        self.FILE_ABS = numpy.chararray(11)
        self.FILE_ABS[1] = ""
        self.FILE_ABS[2] = ""
        self.FILE_ABS[3] = ""
        self.FILE_ABS[4] = ""
        self.FILE_ABS[5] = ""
        self.FILE_ABS[6] = ""
        self.FILE_ABS[7] = ""
        self.FILE_ABS[8] = ""
        self.FILE_ABS[9] = ""
        self.FILE_ABS[10] = ""

        self.FILE_SCR_EXT = numpy.chararray(11)
        self.FILE_SCR_EXT[1] = ""
        self.FILE_SCR_EXT[2] = ""
        self.FILE_SCR_EXT[3] = ""
        self.FILE_SCR_EXT[4] = ""
        self.FILE_SCR_EXT[5] = ""
        self.FILE_SCR_EXT[6] = ""
        self.FILE_SCR_EXT[7] = ""
        self.FILE_SCR_EXT[8] = ""
        self.FILE_SCR_EXT[9] = ""
        self.FILE_SCR_EXT[10] = ""

        self.I_ABS = numpy.zeros(11)
        self.I_ABS[1] = 0
        self.I_ABS[2] = 0
        self.I_ABS[3] = 0
        self.I_ABS[4] = 0
        self.I_ABS[5] = 0
        self.I_ABS[6] = 0
        self.I_ABS[7] = 0
        self.I_ABS[8] = 0
        self.I_ABS[9] = 0
        self.I_ABS[10] = 0

        self.I_SCREEN = numpy.zeros(11)
        self.I_SCREEN[1] = 0
        self.I_SCREEN[2] = 0
        self.I_SCREEN[3] = 0
        self.I_SCREEN[4] = 0
        self.I_SCREEN[5] = 0
        self.I_SCREEN[6] = 0
        self.I_SCREEN[7] = 0
        self.I_SCREEN[8] = 0
        self.I_SCREEN[9] = 0
        self.I_SCREEN[10] = 0

        self.I_SLIT = numpy.zeros(11)
        self.I_SLIT[1] = 0
        self.I_SLIT[2] = 0
        self.I_SLIT[3] = 0
        self.I_SLIT[4] = 0
        self.I_SLIT[5] = 0
        self.I_SLIT[6] = 0
        self.I_SLIT[7] = 0
        self.I_SLIT[8] = 0
        self.I_SLIT[9] = 0
        self.I_SLIT[10] = 0

        self.I_STOP = numpy.zeros(11)
        self.I_STOP[1] = 0
        self.I_STOP[2] = 0
        self.I_STOP[3] = 0
        self.I_STOP[4] = 0
        self.I_STOP[5] = 0
        self.I_STOP[6] = 0
        self.I_STOP[7] = 0
        self.I_STOP[8] = 0
        self.I_STOP[9] = 0
        self.I_STOP[10] = 0

        self.K_SLIT = numpy.zeros(11)
        self.K_SLIT[1] = 0
        self.K_SLIT[2] = 0
        self.K_SLIT[3] = 0
        self.K_SLIT[4] = 0
        self.K_SLIT[5] = 0
        self.K_SLIT[6] = 0
        self.K_SLIT[7] = 0
        self.K_SLIT[8] = 0
        self.K_SLIT[9] = 0
        self.K_SLIT[10] = 0

        self.RX_SLIT = numpy.zeros(11)
        self.RX_SLIT[1] = 0.00000000000000
        self.RX_SLIT[2] = 0.00000000000000
        self.RX_SLIT[3] = 0.00000000000000
        self.RX_SLIT[4] = 0.00000000000000
        self.RX_SLIT[5] = 0.00000000000000
        self.RX_SLIT[6] = 0.00000000000000
        self.RX_SLIT[7] = 0.00000000000000
        self.RX_SLIT[8] = 0.00000000000000
        self.RX_SLIT[9] = 0.00000000000000
        self.RX_SLIT[10] = 0.00000000000000

        self.RZ_SLIT = numpy.zeros(11)
        self.RZ_SLIT[1] = 0.00000000000000
        self.RZ_SLIT[2] = 0.00000000000000
        self.RZ_SLIT[3] = 0.00000000000000
        self.RZ_SLIT[4] = 0.00000000000000
        self.RZ_SLIT[5] = 0.00000000000000
        self.RZ_SLIT[6] = 0.00000000000000
        self.RZ_SLIT[7] = 0.00000000000000
        self.RZ_SLIT[8] = 0.00000000000000
        self.RZ_SLIT[9] = 0.00000000000000
        self.RZ_SLIT[10] = 0.00000000000000

        self.SCR_NUMBER = numpy.zeros(11)
        self.SCR_NUMBER[1] = 0
        self.SCR_NUMBER[2] = 0
        self.SCR_NUMBER[3] = 0
        self.SCR_NUMBER[4] = 0
        self.SCR_NUMBER[5] = 0
        self.SCR_NUMBER[6] = 0
        self.SCR_NUMBER[7] = 0
        self.SCR_NUMBER[8] = 0
        self.SCR_NUMBER[9] = 0
        self.SCR_NUMBER[10] = 0

        self.SL_DIS = numpy.zeros(11)
        self.SL_DIS[1] = 0.00000000000000
        self.SL_DIS[2] = 0.00000000000000
        self.SL_DIS[3] = 0.00000000000000
        self.SL_DIS[4] = 0.00000000000000
        self.SL_DIS[5] = 0.00000000000000
        self.SL_DIS[6] = 0.00000000000000
        self.SL_DIS[7] = 0.00000000000000
        self.SL_DIS[8] = 0.00000000000000
        self.SL_DIS[9] = 0.00000000000000
        self.SL_DIS[10] = 0.00000000000000

        self.THICK = numpy.zeros(11)
        self.THICK[1] = 0.00000000000000
        self.THICK[2] = 0.00000000000000
        self.THICK[3] = 0.00000000000000
        self.THICK[4] = 0.00000000000000
        self.THICK[5] = 0.00000000000000
        self.THICK[6] = 0.00000000000000
        self.THICK[7] = 0.00000000000000
        self.THICK[8] = 0.00000000000000
        self.THICK[9] = 0.00000000000000
        self.THICK[10] = 0.00000000000000

        self.CCC = numpy.zeros(11)
        self.CCC[1] = 0.00000000000000
        self.CCC[2] = 0.00000000000000
        self.CCC[3] = 0.00000000000000
        self.CCC[4] = 0.00000000000000
        self.CCC[5] = 0.00000000000000
        self.CCC[6] = 0.00000000000000
        self.CCC[7] = 0.00000000000000
        self.CCC[8] = 0.00000000000000
        self.CCC[9] = 0.00000000000000
        self.CCC[10] = 0.00000000000000

    def load(self,filename="start.01"):
        self.load_start01(filename=filename)

    def load_start01(self,filename="start.01"):
        a = GFile()
        a.load_gfile((filename))
        dict1 = a.get_as_dictionary()

        for key in dict1.keys():
            if hasattr(self,key):
                # print("assigning: ",key,dict1[key])
                setattr(self, key, dict1[key])
            else: # this is for arrays...
                try:
                    command = "self.%s = %s"%(key,repr(dict1[key]))
                    # print("command: ",command)
                    exec(command)
                except:
                    print("   error executing: ",command)
        # self.THICK[6] = 110.0

if __name__ == "__main__":
    # write start.01 file with shadow4
    import Shadow
    oe_shadow3 = Shadow.OE()
    oe_shadow3.write("start.01")

    # read it with compatibility tools
    oe1 = OE()
    oe1.load(filename="start.01")

    print(dir(oe1))

    print(">>>>>oe1.RX_SLIT",oe1.RX_SLIT)
    print(">>>>>Y_ROT",oe1.Y_ROT )
    print(">>>>>ISTAR1",oe1.ISTAR1 )
    print(">>>>>THICK",oe1.THICK )

    assert(oe1.THICK[6] == oe_shadow3.THICK[6])


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Functions to deal with HSC filters."""

import os
import json

import numpy as np

from astropy.io import fits
from astropy.table import Table
from astropy import constants as const

import matplotlib.pyplot as plt
from matplotlib import rc
from matplotlib import rcParams

import unagi

__all__ = ['filters_to_kcorrect', 'hsc_filters', 'HscFilter', 'SolarSpectrum']

plt.rc('text', usetex=True)
rcParams.update({'xtick.major.pad': '7.0'})
rcParams.update({'xtick.major.size': '7.5'})
rcParams.update({'xtick.major.width': '1.5'})
rcParams.update({'xtick.minor.pad': '7.0'})
rcParams.update({'xtick.minor.size': '3.5'})
rcParams.update({'xtick.minor.width': '1.0'})
rcParams.update({'ytick.major.pad': '7.0'})
rcParams.update({'ytick.major.size': '7.5'})
rcParams.update({'ytick.major.width': '1.5'})
rcParams.update({'ytick.minor.pad': '7.0'})
rcParams.update({'ytick.minor.size': '3.5'})
rcParams.update({'ytick.minor.width': '1.0'})
rcParams.update({'axes.titlepad': '15.0'})
rcParams.update({'font.size': 26})

# Directory that save the transmission curves of the filter
FILTER_DIR = os.path.join(os.path.dirname(unagi.__file__), 'data', 'filters')
# Directory that keeps the solar spectra
SOLAR_DIR = os.path.join(os.path.dirname(unagi.__file__), 'data', 'solar')

# Formal names and nick names of the filters
FILTER_LIST = ['HSC-G', 'HSC-R', 'HSC-R2', 'HSC-I', 'HSC-I2', 'HSC-Z', 'HSC-Y',
               'HSC-NB387', 'HSC-NB816', 'HSC-NB921']
FILTER_SHORT = ['g', 'r', 'r2', 'i', 'i2', 'z', 'y', 'nb387', 'nb816', 'nb921']

# Colors to show the filter
FILTER_COLORS = ['#2ca02c', '#ff7f0e', '#ff7f0e', '#d62728', '#d62728',
                 '#8c564b', '#7f7f7f', 'c', 'm', 'purple']

# AB reference flux in unit of erg/s/cm^2/Hz
AB_FLUX = 3.631e-20
# Speed of light in unit of AA/s
C_AA_PER_SEC = const.c.to('AA/s').value

class Filter(object):
    """Class for organizing HSC filters.

    Please see the following page for more details about HSC filters:
    https://www.subarutelescope.org/Observing/Instruments/HSC/sensitivity.html

    This class is based on the Filter() class from Ben Johnson's sedpy
    https://github.com/bd-j/sedpy/blob/master/sedpy/observate.py
    """
    def __init__(self, band, origin=False, center=False):
        """Read in the HSC filter transmission curves.
        """
        if band.strip().upper() in FILTER_LIST:
            self.index = FILTER_LIST.index(band.strip().upper())
        elif band.strip().lower() in FILTER_SHORT:
            self.index = FILTER_SHORT.index(band.strip().lower())
        else:
            raise NameError("# Wrong filter name!")

        # Name of the filter
        self.filter = FILTER_LIST[self.index]
        self.short = FILTER_SHORT[self.index]

        # Color to show the filter
        self.color = FILTER_COLORS[self.index]

        # Old or new version of r- and i-band filters
        if self.short == 'r' or self.short == 'i':
            self.version = 'old'
        else:
            self.version = 'new'

        # Whether the transmission curve is the total one or the just-filter one
        if origin:
            # This is the "origin" filter response curve from the HSC webpage:
            # https://www.subarutelescope.org/Observing/Instruments/HSC/sensitivity.html
            # Type of the transmission curve: area weighted one or for center
            filter_dir = os.path.join(FILTER_DIR, 'origin')
            self.type = 'just_filter'
            self.center = False
            if center:
                # This is for the center of the camera
                self.center = True
                self.name = '{}.txt'.format(self.filter)
            else:
                # This is the area-weighted mean one
                self.center = False
                self.name = 'w{}.txt'.format(self.filter)
        else:
            # This is the total transmission curves from Kawanomoto et al. 2018
            # Downloaded from here:
            # https://hsc-release.mtk.nao.ac.jp/doc/wp-content/uploads/2019/04/hsc_responses_all_rev3.tar.gz
            # Assume airmass=1.2 and PWV=1.5
            filter_dir = os.path.join(FILTER_DIR, 'total')
            self.type = 'total'
            self.name = 'hsc_{}_v2018.dat'.format(self.short)

        # Find the file and read in the transmission curve
        self.filename = os.path.join(filter_dir, self.name)
        self.wave, self.trans = self._load_filter()
        self.npts = len(self.wave)

        # Basic properties of the filter
        self.wave_effective = None
        self.wave_pivot = None
        self.wave_mean = None
        self.wave_average = None
        self.rectangular_width = None
        self.gauss_width = None
        self.effective_width = None
        self._basic_properties()

        # AB Zeropoint in counts
        self.ab_zero_counts = self._counts(
            self.wave, AB_FLUX * C_AA_PER_SEC / (self.wave ** 2))

        # AB absolute magnitude of Sun in this band
        self.solar_ab_mag = self._solar_ab_mag(kind='Willmer2018')

        # Convert to Kcorrect filter format
        self.to_kfilter()

        # Save it as a JSON file
        self.to_json()

    def _load_filter(self):
        """Load and process the transimission curve."""
        if not os.path.isfile(self.filename):
            raise IOError("# Cannot find the response curve file {}".format(self.filename))

        # Read in the .txt response curve
        wave, trans = np.genfromtxt(self.filename, usecols=(0, 1), unpack=True)

        use = np.isfinite(trans) & (trans >= 0)
        order = wave[use].argsort()
        wave = wave[use][order]
        trans = trans[use][order]

        return wave, trans

    def print(self):
        """Print out basic properties of the filter."""
        print("# Filter: {}".format(self.filename))
        print("# Filter type: {}".format(self.type))
        print("# Version: {}".format(self.version))
        print("# Effective wavelength   : {:8.3f} Angstrom".format(self.wave_effective))
        print("# Pivot wavelength       : {:8.3f} Angstrom".format(self.wave_pivot))
        print("# Mean wavelength        : {:8.3f} Angstrom".format(self.wave_mean))
        print("# Effective width        : {:8.3f} Angstrom".format(self.effective_width))
        print("# Rectangular width      : {:8.3f} Angstrom".format(self.rectangular_width))
        print("# AB absolute magnitude of the sun : {:6.3f} mag".format(self.solar_ab_mag))

    def plot(self):
        """Plot the transmission curves of the filter."""
        fig = plt.figure(figsize=(8, 5))
        ax1 = fig.add_subplot(111)
        ax1.grid(linestyle='--', linewidth=2, alpha=0.8)
        ax1.axhline(0.0, linewidth=2, color='k', alpha=0.8)
        # Filled the transmission curve
        ax1.fill_between(self.wave, 0.0, self.trans, edgecolor='k', alpha=0.4,
                         linewidth=2.5, facecolor=FILTER_COLORS[self.index])
        ax1.text(0.9, 0.85, r'$\rm {}$'.format(self.short), transform=ax1.transAxes)
        # Highlight the effective and pivot wavelength
        ax1.axvline(self.wave_effective, linewidth=2.0, linestyle='--',
                    label=r'$\lambda_{\rm eff}$')
        ax1.axvline(self.wave_pivot, linewidth=2.0, linestyle='-.',
                    label=r'$\lambda_{\rm piv}$')
        _ = ax1.set_xlabel(r'$\mathrm{Wavelength}\ [\AA]$')
        _ = ax1.set_ylabel(r'$\mathrm{Transmission}$')
        ax1.legend(loc='lower right', fontsize=18)

        return fig

    def _basic_properties(self):
        """Get the basic properties of the filter.

        These properties include several 'effective' wavelength definitions and
        several width definitions.

        See Fukugita et al. (1996) AJ 111, 1748 for discussion and definition
        of many of these quantities.
        """
        # Calculate some useful integrals
        i0 = np.trapz(self.trans * np.log(self.wave), np.log(self.wave))
        i1 = np.trapz(self.trans, np.log(self.wave))
        i2 = np.trapz(self.trans * self.wave, self.wave)
        i3 = np.trapz(self.trans, self.wave)

        # Effective wavelength
        self.wave_effective = np.exp(i0 / i1)
        # Pivot wavelength
        self.wave_pivot = np.sqrt(i2 / i1)
        # Mean wavelength
        self.wave_mean = self.wave_effective
        self.wave_average = i2 / i3
        # Rectangular width of the filter
        self.rectangular_width = i3 / self.trans.max()

        i4 = np.trapz(self.trans * (np.log(self.wave / self.wave_effective)) ** 2.0,
                      np.log(self.wave))

        # Gaussian width of the filter
        self.gauss_width = (i4 / i1) ** (0.5)
        # Effectove width of the filter
        self.effective_width = (2.0 * np.sqrt(2. * np.log(2.)) *
                                self.gauss_width * self.wave_effective)

    def _counts(self, obj_wave, obj_flux):
        """Project source spectrum onto filter and return the detector signal.
        """
        # Interpolate filter transmission to source spectrum
        newtrans = np.interp(obj_wave, self.wave, self.trans, left=0., right=0.)

        # Integrate lambda * f_lambda * R
        if True in (newtrans > 0.):
            positive = np.where(newtrans > 0.)[0]
            ind = slice(max(positive.min() - 1, 0),
                        min(positive.max() + 2, len(obj_wave)))
            counts = np.trapz(obj_wave[ind] * newtrans[ind] *
                              obj_flux[..., ind], obj_wave[ind], axis=-1)
            return np.squeeze(counts)

        return float('NaN')

    def _solar_ab_mag(self, kind='Willmer2018'):
        """Get the absolute magnitude of Sun in this band.
        """
        # Get the solar spectrum
        solar_spectrum = SolarSpectrum(kind=kind)
        counts = self._counts(solar_spectrum.wave, solar_spectrum.flux)
        return -2.5 * np.log10(counts / self.ab_zero_counts)

    def to_kfilter(self):
        """Convert the filter transmission curve into Kcorrect format.
        """
        _ = filters_to_kcorrect(self.filename)

    def as_dict(self):
        """Convert it into a dict."""
        filter_dict = self.__dict__
        filter_dict['wave'] = list(filter_dict['wave'])
        filter_dict['trans'] = list(filter_dict['trans'])
        return filter_dict

    def to_json(self):
        """Save as a JSON file."""
        pre, _ = os.path.splitext(self.filename)
        json_out = pre + '.json'
        with open(json_out, 'w') as jf:
            json.dump(self.as_dict(), jf)

def hsc_filters(origin=False, center=False, use_saved=True):
    """Get the tabel that summarizes information about HSC filters."""
    if origin:
        if center:
            tab_id = 'origin_center'
            xml_table = os.path.join(FILTER_DIR, 'hsc_filters_origin_center.xml')
        else:
            tab_id = 'origin_weighted'
            xml_table = os.path.join(FILTER_DIR, 'hsc_filters_origin_weighted.xml')
    else:
        tab_id = 'total'
        xml_table = os.path.join(FILTER_DIR, 'hsc_filters_total.xml')

    # If already created, just read it in
    if os.path.isfile(xml_table) and use_saved:
        return Table.read(xml_table, format='votable', table_id=tab_id)
    else:
        # Summarize the filters
        # Total transmission curves
        f_table = Table(
            [Filter(f, origin=origin, center=center).as_dict() for f in FILTER_LIST])
        f_table.write(xml_table, format='votable', table_id=tab_id, overwrite=True)
        return f_table

def filters_to_kcorrect(curve_file, verbose=False):
    """
    Convert a filter response curve to the Kcorrect format.

    This is used by Kcorrect and iSEDFit.
    """
    if not os.path.isfile(curve_file):
        raise IOError("# Cannot find the response curve file {}".format(curve_file))

    # Read in the .txt response curve
    wave, response = np.genfromtxt(curve_file, usecols=(0, 1), unpack=True)

    # Output file name
    prefix, _ = os.path.splitext(curve_file)
    output_par = prefix + '.par'

    if os.path.isfile(output_par):
        if verbose:
            print("# Curve {0} is already available".format(output_par))
    else:
        assert len(wave) == len(response), '''
            Wavelength and response curve should have the same size'''

        par = open(output_par, 'w')
        par.write(
            "# %s\n typedef struct {\n  double lambda;\n  double pass;\n } KFILTER;\n\n")
        for w, r in zip(wave, response):
            par.write("KFILTER %10.4f %11.7f\n" % (w, r))
        par.close()

    return wave, response

class SolarSpectrum(object):
    """Class to hold solar spectrum.

    On deriving Solar absolute magnitude:
    ------------------------------------
    To derive the Sun's absolute magnitudes, the IAU 2012 definitions of the astronomical unit
    (au) (Prša et al. 2016) and parsec were used, giving a distance modulus for the
    Sun of −31.5721 mag.

    To rationalize the use of solar constants, the IAU in 2015 adopted a nominal value for the Sun's
    luminosity L⊙ = 3.828 × 10^8 W (Prša et al. 2016), which corresponds to an average
    TSI of 1361 W m−2 at 1 au and an absolute bolometric magnitude of MBol = 4.74.
    """
    def __init__(self, kind='Willmer2018'):
        """Read in the solar spectrum and process it."""
        # Conversion to d=10 pc from 1 AU
        au_to_10pc = (1.0 / (3600 * 180 / np.pi * 10)) ** 2.0

        if kind.strip() == 'Willmer2018':
            # This is based on the work by Willmer 2018:
            #   https://iopscience.iop.org/article/10.3847/1538-4365/aabfdf
            # More information can be found on this webpage:
            #   http://mips.as.arizona.edu/~cnaw/sun.html
            # About the spectrum:
            #   The solar SED used here also combines observations with model spectra.
            #   The observed spectrum is a composite calculated by Haberreiter et al. (2017)
            #   using data from over 20 space-based instruments for an arbitrary date
            #   (2008 December 19, JDN = 2454820) during the solar minimum.
            #   The absolute calibration is set using the ${ATLAS}\,3$ composite spectrum
            #   of Thuillier et al. (2004), and constraining the Total Solar Irradiance (TSI)
            #   to the value measured for each day by Dudok de Wit et al. (2017).
            #   The observed composite ends at ~2.0 μm, and to extend the SED into the infrared;
            #   the model spectra of Fontenla et al. (2011) and Kurucz (2011) are used.
            self.file = os.path.join(SOLAR_DIR, 'sun_composite.fits')
            if os.path.isfile(self.file):
                solar_fits = fits.open(self.file)[1].data
                # Wavelength
                self.wave = solar_fits['WAVE']
                self.wave_unit = 'Angstrom'
                self.flux = solar_fits['FLUX'] * au_to_10pc
                self.flux_unit = 'erg/s/cm^2/AA'
            else:
                raise IOError("# Cannot find the solar spectrum {}".format(self.file))
        elif kind.strip() == 'Kurucz1993':
            # This is the theoretical spectrum of the Sun from Kurucz
            # from: ftp://ftp.stsci.edu/cdbs/grid/k93models/standards/sun_kurucz93.fits
            # The theoretical spectrum is scaled to match the observed spectrum
            # from 1.5 - 2.5 microns, and then it is used where the observed spectrum ends.
            # The theoretical model of the Sun from Kurucz93 atlas using the following
            # parameters when the Sun is at 1 au.
            #   log_Z T_eff log_g V_{Johnson} +0.0 5777 +4.44 -26.75
            self.file = os.path.join(SOLAR_DIR, 'sun_kurucz93.fits')

            # This file should be in AA and erg/s/cm^2/AA at 1AU
            if os.path.isfile(self.file):
                solar_fits = fits.open(self.file)[1].data
                # Wavelength
                self.wave = solar_fits['WAVELENGTH']
                self.wave_unit = 'Angstrom'
                self.flux = solar_fits['FLUX'] * au_to_10pc
                self.flux_unit = 'erg/s/cm^2/AA'
            else:
                raise IOError("# Cannot find the solar spectrum {}".format(self.file))
        else:
            raise NameError("# Wrong type of Solar spectrum: [Willmer2018|Kurucz1993]")

        self.wave_min, self.wave_max = self.wave.min(), self.wave.max()
        self.solar = np.column_stack((self.wave, self.flux))


# -*- coding: utf-8 -*-
"""
DJI D-Gamut Colourspace
=======================

Defines the *DJI D-Gamut* colourspace:

-   :attr:`colour.models.DJI_D_GAMUT_COLOURSPACE`.

References
----------
-   :cite:`DJI2017` : Dji. (2017). White Paper on D-Log and D-Gamut of DJI
    Cinema Color System. Retrieved from https://dl.djicdn.com/downloads/\
zenmuse+x7/20171010/D-Log_D-Gamut_Whitepaper.pdf
"""

from __future__ import division, unicode_literals

import numpy as np

from colour.colorimetry import ILLUMINANTS
from colour.models.rgb import (RGB_Colourspace, log_encoding_DJIDLog,
                               log_decoding_DJIDLog)

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2020 - Colour Developers'
__license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-developers@colour-science.org'
__status__ = 'Production'

__all__ = [
    'DJI_D_GAMUT_PRIMARIES', 'DJI_D_GAMUT_WHITEPOINT_NAME',
    'DJI_D_GAMUT_WHITEPOINT', 'DJI_D_GAMUT_TO_XYZ_MATRIX',
    'XYZ_TO_DJI_D_GAMUT_MATRIX', 'DJI_D_GAMUT_COLOURSPACE'
]

DJI_D_GAMUT_PRIMARIES = np.array([
    [0.71, 0.31],
    [0.21, 0.88],
    [0.09, -0.08],
])
"""
*DJI D-Gamut* colourspace primaries.

DJI_D_GAMUT_PRIMARIES : ndarray, (3, 2)
"""

DJI_D_GAMUT_WHITEPOINT_NAME = 'D65'
"""
*DJI D-Gamut* colourspace whitepoint name.

DJI_D_GAMUT_WHITEPOINT : unicode
"""

DJI_D_GAMUT_WHITEPOINT = (ILLUMINANTS['CIE 1931 2 Degree Standard Observer'][
    DJI_D_GAMUT_WHITEPOINT_NAME])
"""
*DJI D-Gamut* colourspace whitepoint.

DJI_D_GAMUT_WHITEPOINT : ndarray
"""

DJI_D_GAMUT_TO_XYZ_MATRIX = np.array([[0.6482, 0.1940,
                                       0.1082], [0.2830, 0.8132, -0.0962],
                                      [-0.0183, -0.0832, 1.1903]])
"""
*DJI D-Gamut* colourspace to *CIE XYZ* tristimulus values matrix.

DJI_D_GAMUT_TO_XYZ_MATRIX : array_like, (3, 3)
"""

XYZ_TO_DJI_D_GAMUT_MATRIX = np.array([[1.7257, -0.4314,
                                       -0.1917], [-0.6025, 1.3906, 0.1671],
                                      [-0.0156, 0.0905, 0.8489]])
"""
*CIE XYZ* tristimulus values to *DJI D-Gamut* colourspace matrix.

XYZ_TO_DJI_D_GAMUT_MATRIX : array_like, (3, 3)
"""

DJI_D_GAMUT_COLOURSPACE = RGB_Colourspace(
    'DJI D-Gamut',
    DJI_D_GAMUT_PRIMARIES,
    DJI_D_GAMUT_WHITEPOINT,
    DJI_D_GAMUT_WHITEPOINT_NAME,
    DJI_D_GAMUT_TO_XYZ_MATRIX,
    XYZ_TO_DJI_D_GAMUT_MATRIX,
    log_encoding_DJIDLog,
    log_decoding_DJIDLog,
)
DJI_D_GAMUT_COLOURSPACE.__doc__ = """
*DJI_D-Gamut* colourspace.

    References
    ----------
    :cite:`DJI2017`

DJI_D_GAMUT_COLOURSPACE : RGB_Colourspace
"""