from __future__ import division
import sys
import json
import numpy as np
from functools import partial
import matplotlib as mpl
import os
mpl.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.animation as animation

#--------------------------------------------------------------------------------------
# This plotter assumes the presence of the following data file structure:
#
# - All output files are located inside a subdir of the working dir named <name>
#
# - The subdir <name> contains a preamble file named "name.pre"
#
# - Each run is recorded in a data file named "name_i.dat", where $i$ is the run index
#
# - The preamble format is a Python DICT with the following standard keys:
#   'Nruns'             -   the number of runs recorded in this directory
#   'name'              -   the string identifier <name>
#   'ex_dataQ'          -   Boolean indicating whether or not experiment update cycle
#                           data was recorded in addition to the experiment state
#   'agent_dataQ'       -   Boolean indicating whether or not per-agent update cycle
#                           data was recorded in addition to the experiment state
#   'mids_to_record'    -   List of mids (belonging to the experiment) whose values
#                           were recorded (for each cycle of each run)
#
# - Additional preamble values are experiment-dependent.
#   For SNIFFY, we have:
#   'env_length'        -   The size of SNIFFY's environment
#   'total_cycles'      -   Length (in cycles) of the whole run
#--------------------------------------------------------------------------------------

def get_pickles(infile):
    for item in infile:
        yield json.loads(item)

def compi(x):
    if type(x)==type(0):
        return x+1 if x%2==0 else x-1
    else:
        raise Exception('Input to \"compi\" must be an integer! \n')

def fcomp(x):
    #assuming x is a footprint and an np.array:
    return 1-x

# inequality check for qualitative weights: "IS x strictly less than y?"
def qless(x,y):
    if x<0: #infinity is never less than anything
        return False
    elif y<0: #anything finite is less than infinity
        return True
    else: #finite things compared as usual
        return x<y

# max function for qualitative weights
def qmax(*args):
    if min(args)<0:
        return -1
    else:
        return max(args)

def qmin(*args):
    if max(args)<0:
        return -1
    else:
        return min(filter(lambda x: x>=0, args))

# convert npdirs data into a matrix
def convert_full_implications(matr):
    L=len(matr)
    for i in range(L):
        for j in range(L):
            if j >= len(matr[i]):
                matr[i].append(matr[compi(j)][compi(i)])
    return np.matrix(matr,dtype=int)

# convert dirs data into a matrix
def convert_raw_implications(matr):
    L=len(matr)
    for i in range(L):
        for j in range(L):
            if j >= len(matr[i]):
                try:
                    matr[i].append(matr[compi(j)][compi(i)])
                except IndexError:
                    matr[i].append(False)
    return np.matrix(matr,dtype=int)

# convert weights data into a matrix
def convert_weights(matr):
    L=len(matr)
    newmatr=[[] for ind in xrange(L)]
    for i in xrange(L):
        for j in xrange(L):
            if j>=len(matr[i]):
                newmatr[i].append(matr[j][i])
            else:
                newmatr[i].append(matr[i][j])
    return np.matrix(newmatr)

def ellone(x,y):
    #assuming x,y are np arrays of the same shape,
    #return the ell-1 distance between them:
    return np.sum(np.abs(x-y))

#
# Read the preamble (GENERIC)
#

NAME=sys.argv[1]

preamble_file_name = os.path.join(NAME, NAME+".pre")
preamblef=open(preamble_file_name,'rb')
preamble=json.load(preamblef)
preamblef.close()

RUN_NAME=lambda i: NAME+"_"+str(i)
input_file_name=lambda i: os.path.join(NAME, RUN_NAME(i) + ".dat")
supp_file_name=lambda i: os.path.join(NAME, RUN_NAME(i) + ".sup")
NRUNS=preamble['Nruns']

#
# Open the data files (GENERIC)
#

input_file={}
for ind in xrange(NRUNS):
    input_file[ind]=open(input_file_name(ind),'rb')

supp_file={}
for ind in xrange(NRUNS):
    supp_file[ind]=open(supp_file_name(ind),'rb')

#
# Prepare data entries (GENERIC)
#

DATA={}

#- prepare entries for experiment measurables
if preamble['mids_recorded'] is []:
    pass
else:
    for mid in preamble['mids_recorded']:
        DATA[mid]=[[] for ind in xrange(NRUNS)]

#- prepare entries for update cycle reports
if bool(preamble['ex_dataQ']):
    for mid in preamble['ex_data_recorded']:
        DATA[mid]=[[] for ind in xrange(NRUNS)]

#- prepare entries for per-agent update cycle reports
if bool(preamble['agent_dataQ']):
    for mid in preamble['agent_data_recorded']:
        for agent_id in preamble['agents']:
            DATA[(agent_id,mid)]=[[] for ind in xrange(NRUNS)]

#
# Read the data from the .dat files (GENERIC)
#
SUPP={}
for ind in xrange(NRUNS):
    #load data from the supplementary files:
    SUPP[ind]=json.loads(supp_file[ind].readline())

    #load data from the data files:
    for record in get_pickles(input_file[ind]):
        #- read entries for experiment measurables
        if preamble['mids_recorded'] is []:
            pass
        else:
            for mid,item in zip(preamble['mids_recorded'],record['mids_recorded']):
                DATA[mid][ind].append(item)
        #- read entries for experiment update cycle data
        if bool(preamble['ex_dataQ']):
            for tag,item in zip(preamble['ex_data_recorded'],record['ex_data_recorded']):
                DATA[tag][ind].append(item)
        #- read entries for experiment update cycle data
        if bool(preamble['agent_dataQ']):
            for agent_id in preamble['agents']:
                for tag,item in zip(preamble['agent_data_recorded'],record['agent_data_recorded'][agent_id]):
                    DATA[(agent_id,tag)][ind].append(item)

# close the data & supplementary files:
for ind in xrange(NRUNS):
    input_file[ind].close()
    supp_file[ind].close()

#------------------------------------------------------------------------------------
# At this point, each DATA[tag] item is a 2-dim Python list object,
# with the tags taking the form of:
# - an experiment measurable id;
# - a measurement tag from the update cycle (time stamp, decision, etc.);
# - a double tag of the form (agent_id,tag) indicating an agent-specific measurement
#   from that agent's update cycle.
#
# From this point on, all instructions are specific to the experiment at hand
#------------------------------------------------------------------------------------

#
# Prepare the plots (EXPERIMENT-SPECIFIC)
#

AGENT_TYPES={
    '_Q':['qualitative','xkcd:red'],
    '_Eu':['empirical uniform','xkcd:sky blue'],
    '_Ev':['empirical value-based','xkcd:blue'],
    '_Du':['discounted uniform','xkcd:forest green'],
    '_Dv':['discounted value-based','xkcd:green'],
    }
#ORDERED_TYPES=['_Q','_Eu','_Ev','_Du','_Dv']
ORDERED_TYPES=['_Eu','_Du']
#ORDERED_TYPES=['_Q','_Ev','_Dv']
NTYPES=len(ORDERED_TYPES)

# length of the environment (due to differences between circle and interval)
env_length=lambda ind,typ: len(SUPP[ind]['values'][typ])
# duration of the experiment for each run
duration=lambda ind: len(DATA['counter'][ind])
def duration_gen():
    for ind in xrange(NRUNS):
        yield duration(ind)
DURATION=max(duration_gen())

# value of each position in the environment, needed as np.array, for each run and type
vm=lambda ind,typ: np.array(SUPP[ind]['values'][typ])
# extreme (=target) value of signal for each run and type
v_extreme=lambda ind,typ: vm(ind,typ).min() if typ=='_Q' else vm(ind,typ).max()

# sensor footprints for each run
fp=lambda ind,sensor_ind: np.array(SUPP[ind]['footprints'][sensor_ind]) #compute a footprint vector
# number of sensor for each run
Nsensors=lambda ind: len(SUPP[ind]['footprints'])   #the number of footprint vectors
def Nsensors_gen():
    for ind in xrange(NRUNS):
        yield Nsensors(ind)
NSENSORS=max(Nsensors_gen())

# [initial] implication threshold for each run
threshold=lambda ind: SUPP[ind]['threshold']

#Form the implications matrices
#
#- check for inclusions among footprints:
std_imp_check=lambda x,y: all(x<=y)
#- check for ground truth thresholded inclusions
def lookup_val(x,y,ind,typ):
    if typ=='_Q':
        return -1 if not sum(x*y) else np.extract(x*y,vm(ind,typ)).min()
    else:
        return (0.+sum(x*y*vm(ind,typ)))/env_length(ind,typ)

def imp_check(x,y,ind,typ):
    XY=lookup_val(x,y,ind,typ)
    X_Y=lookup_val(fcomp(x),y,ind,typ)
    X_Y_=lookup_val(fcomp(x),fcomp(y),ind,typ)
    XY_=lookup_val(x,fcomp(y),ind,typ)
    total=sum(vm(ind,typ))
    if typ=='_Q': #qualitative implication (zero threshold)
        return qless(qmax(XY,X_Y_),XY_)
    else: #real-valued (statistical) implication
        return XY_<min(total*threshold(ind),XY,X_Y_,X_Y) or (XY_==0 and X_Y==0)# and XY>0 and X_Y_>0)

#- construct the ground truth PCR matrices for each run and type
GROUND_WEIGHTS={typ:[] for typ in ORDERED_TYPES}
GROUND_RAW_IMPS={typ:[] for typ in ORDERED_TYPES}
ABS_GROUND_RAW_IMPS=[]
for typ in ORDERED_TYPES:
    for ind in xrange(NRUNS):
        lookup=lambda x,y: lookup_val(x,y,ind,typ)
        check=lambda x,y: imp_check(x,y,ind,typ)
        # Weight matrix computed from known values of states
        GROUND_WEIGHTS[typ].append(np.matrix([[lookup(fp(ind,yind),fp(ind,xind)) for xind in xrange(Nsensors(ind))] for yind in xrange(Nsensors(ind))]))
        # PCR matrix computed from known values of states; note the transpose!!
        GROUND_RAW_IMPS[typ].append(np.matrix([[check(fp(ind,yind),fp(ind,xind)) for xind in xrange(Nsensors(ind))] for yind in xrange(Nsensors(ind))],dtype=int))
for ind in xrange(NRUNS):
    ABS_GROUND_RAW_IMPS.append(np.matrix([[std_imp_check(fp(ind,yind),fp(ind,xind)) for xind in xrange(Nsensors(ind))] for yind in xrange(Nsensors(ind))],dtype=int))

#- construct matrices from data
WEIGHTS={typ:[[] for ind in xrange(NRUNS)] for typ in ORDERED_TYPES}
RAW_IMPS={typ:[[] for ind in xrange(NRUNS)] for typ in ORDERED_TYPES}
FULL_IMPS={typ:[[] for ind in xrange(NRUNS)] for typ in ORDERED_TYPES}
WEIGHT_DIFFS={typ:[[] for ind in xrange(NRUNS)] for typ in ORDERED_TYPES}
RAW_DIFFS={typ:[[] for ind in xrange(NRUNS)] for typ in ORDERED_TYPES}
FULL_DIFFS={typ:[[] for ind in xrange(NRUNS)] for typ in ORDERED_TYPES}
STD_DIFFS={typ:[[] for ind in xrange(NRUNS)] for typ in ORDERED_TYPES}
for typ in ORDERED_TYPES:
    for ind in xrange(NRUNS):
        for t in xrange(DURATION):
            #- learned weight matrix at time t:
            tmp_raw_weights=convert_weights(DATA[('obs'+typ,'weights')][ind][t]['minus'])
            WEIGHTS[typ][ind].append(tmp_raw_weights)

            #- learned PCR structure (learned raw implications) at time t:
            tmp_raw_imps=convert_raw_implications(DATA[('obs'+typ,'raw_implications')][ind][t]['minus'])
            RAW_IMPS[typ][ind].append(tmp_raw_imps)

            #- transitive closure of learned PCR at time t:
            tmp_full_imps=convert_full_implications(DATA[('obs'+typ,'full_implications')][ind][t]['minus'])
            FULL_IMPS[typ][ind].append(tmp_full_imps)

            #- ell-1 distance of learned weights to ground truth weights
            WEIGHT_DIFFS[typ][ind].append((np.abs(tmp_raw_weights-GROUND_WEIGHTS[typ][ind])).max())
            #- ell-1 distance of learned PCR to ground truth PCR
            RAW_DIFFS[typ][ind].append(ellone(tmp_raw_imps,GROUND_RAW_IMPS[typ][ind]))
            #- ell-1 distance of transitive closure to ground truth PCR (could be quite bigger)
            FULL_DIFFS[typ][ind].append(ellone(tmp_full_imps,GROUND_RAW_IMPS[typ][ind]))
            STD_DIFFS[typ][ind].append(ellone(tmp_full_imps,ABS_GROUND_RAW_IMPS[ind]))

#
#Initialize the plots
#

#- initialize figure
fig,ax_imps=plt.subplots(nrows=len(ORDERED_TYPES),ncols=1,sharex=True,sharey=True)
fig.suptitle('# of incorrect implications over time',fontsize=10)
plt.subplots_adjust(left=0.05,right=0.95,bottom=0.05,top=0.95)
plt.xlabel('time elapsed (cycles)',fontsize=10)

#- form the implications plots
AX={}
t=xrange(DURATION)
for typ,ax in zip(ORDERED_TYPES,ax_imps):
    AX[typ]=ax
    ax.set_ylabel('% incorrect implications',fontsize=10)

    SKIP=DURATION/100
    OFFSET=1

    weight_diffs=np.array(WEIGHT_DIFFS[typ])
    raw_diffs=np.array(RAW_DIFFS[typ])/pow(NSENSORS,2)
    full_diffs=np.array(FULL_DIFFS[typ])/pow(NSENSORS,2)
    std_diffs=np.array(STD_DIFFS[typ])/pow(NSENSORS,2)
    #YMAX=max(np.max(raw_diffs),np.max(full_diffs),np.max(std_diffs))
    ax.set_ylim(bottom=0.,top=1.)

    #means over runs
    diff_weight_mean=np.mean(weight_diffs,axis=0)
    diff_raw_mean=np.mean(raw_diffs,axis=0)
    diff_full_mean=np.mean(full_diffs,axis=0)
    diff_std_mean=np.mean(std_diffs,axis=0)
    #standard deviations over runs
    diff_weight_sdv=np.std(weight_diffs,axis=0)
    diff_raw_sdv=np.std(raw_diffs,axis=0)
    diff_full_sdv=np.std(full_diffs,axis=0)
    diff_std_sdv=np.std(std_diffs,axis=0)

    ALPH=0.7 #foreground transparency coefficients
    BETA=0.2 #background transparency coefficients

    ax.fill_between(t,diff_weight_mean-diff_weight_sdv,diff_weight_mean+diff_weight_sdv,alpha=BETA,color=AGENT_TYPES[typ][1])
    ax.fill_between(t,diff_raw_mean-diff_raw_sdv,diff_raw_mean+diff_raw_sdv,alpha=BETA,color=AGENT_TYPES[typ][1])
    #ax.fill_between(t,diff_full_mean-diff_full_sdv,diff_full_mean+diff_full_sdv,alpha=BETA,color=AGENT_TYPES[typ][1])
    #ax.fill_between(t,diff_std_mean-diff_std_sdv,diff_std_mean+diff_std_sdv,alpha=BETA,color=AGENT_TYPES[typ][1])

    ax.plot(t,diff_weight_mean,linestyle='--',linewidth=3,color=AGENT_TYPES[typ][1],alpha=ALPH,label='Learned weights vs. Ground weights, '+AGENT_TYPES[typ][0])
    ax.plot(t,diff_raw_mean,linestyle='solid',linewidth=3,color=AGENT_TYPES[typ][1],alpha=ALPH,label='Learned PCR vs. Ground PCR, '+AGENT_TYPES[typ][0])
    #ax.plot(t,diff_full_mean,linestyle='dashed',linewidth=3,color=AGENT_TYPES[typ][1],alpha=ALPH,label='Full implications vs. Ground PCR, '+AGENT_TYPES[typ][0])
    #ax.plot(t,diff_std_mean,linestyle='dotted',linewidth=2,color=AGENT_TYPES[typ][1],alpha=ALPH,label='Full implications vs. Set implications, '+AGENT_TYPES[typ][0])

    ax.legend()
#ax_imps.legend()

#Show the plots
plt.show()


#!/usr/bin/env python
# -*- coding: utf-8 -*

import os
import os.path
import re
import scipy.io as sio
import numpy as np
from mpi4py import MPI
from pyscf import gto

import decodense

# decodense variables
PARAMS = {
    'prop': 'energy',
    'basis': 'ccpvdz',
    'xc': 'pbe0',
    'loc': 'ibo-2',
    'pop': 'iao',
    'part': 'atoms'
}
UNIT = 'au'
N_ATOMS = 3
RST_FREQ = 50

# input / output
INPUT = os.getcwd() + '/water_therm_1000.mat'
OUTPUT = os.getcwd() + '/{:}_{:}_{:}_{:}_{:}_{:}/'.format(PARAMS['prop'], PARAMS['xc'] if PARAMS['xc'] != '' else 'hf', \
                                                          PARAMS['basis'], PARAMS['loc'] if PARAMS['loc'] != '' else 'can', \
                                                          PARAMS['pop'], PARAMS['part'], PARAMS['prop'])

def main():
        """
        main program
        """
        # mpi attributes
        comm = MPI.COMM_WORLD
        stat = MPI.Status()
        rank = comm.Get_rank()
        size = comm.Get_size()
        assert 1 < size, 'script must be run in parallel: `mpiexec -np N ...`'

        # init decomp object
        decomp = decodense.DecompCls(**PARAMS)

        # master
        if rank == 0:

            # write MPI parameters
            print('\n MPI global size = {:}\n'.format(size))

            # make output dir
            if not os.path.isdir(OUTPUT):
                restart = False
                os.mkdir(OUTPUT)
            else:
                restart = True
            # load in dataset
            data = sio.loadmat(INPUT)
            # number of slaves and tasks
            n_slaves = size - 1
            n_tasks = data['R'].shape[0]

            # start_idx
            if restart:
                res_el = np.load(OUTPUT + 'elec.npy')
                res_nuc = np.load(OUTPUT + 'nuc.npy')
                start_idx = np.argmax(res_el[:, 0] == 0.)
            else:
                res_el = np.zeros([n_tasks, N_ATOMS], dtype=np.float64)
                res_nuc = np.zeros([n_tasks, N_ATOMS], dtype=np.float64)
                start_idx = 0

            # loop over molecules in data set
            for mol_idx, mol_geo in enumerate(data['R'][start_idx:], start_idx):

                # probe for available slaves
                comm.Probe(source=MPI.ANY_SOURCE, tag=1, status=stat)
                # receive slave results
                res = comm.recv(source=stat.source, tag=1)
                # retrieve results
                if res is not None:
                    res_el[res['idx']] = res['prop_el']
                    res_nuc[res['idx']] = res['prop_nuc']
                    if res['idx'] % RST_FREQ == 0:
                        # save results
                        np.save(OUTPUT + 'elec', res_el)
                        np.save(OUTPUT + 'nuc', res_nuc)
                        # print status
                        prog = (res['idx'] + 1) / n_tasks
                        status = int(round(50 * prog))
                        remainder = (50 - status)
                        print(' STATUS:   [{:}]   ---  {:>6.2f} %'.format('#' * status + '-' * remainder, prog * 100.))

                # send mol_dict to slave
                comm.send({'idx': mol_idx, \
                           'struct': [[int(z), mol_geo[i]] for i, z in enumerate(data['Z'][mol_idx]) if 0. < z]}, \
                          dest=stat.source, tag=2)

            # done with all tasks
            while n_slaves > 0:

                # probe for available slaves
                comm.Probe(source=MPI.ANY_SOURCE, tag=1, status=stat)
                # receive slave results
                res = comm.recv(source=stat.source, tag=1)
                # save results
                if res is not None:
                    res_el[res['idx']] = res['prop_el']
                    res_nuc[res['idx']] = res['prop_nuc']
                    if res['idx'] % RST_FREQ == 0:
                        np.save(OUTPUT + 'elec', res_el)
                        np.save(OUTPUT + 'nuc', res_nuc)

                # send exit signal to slave
                comm.send(None, dest=stat.source, tag=2)
                # remove slave
                n_slaves -= 1

            # save final results
            np.save(OUTPUT + 'elec', res_el)
            np.save(OUTPUT + 'nuc', res_nuc)
            # print final status
            print(' STATUS:   [{:}]   ---  {:>6.2f} %'.format('#' * 50 + '-' * 0, 100.))
            # write final info
            with open(OUTPUT + 'info.txt', 'w') as f_info:
                f_info.write(decodense.info(decomp))

        else: # slaves

            # send availability to master
            comm.send(None, dest=0, tag=1)

            # receive work from master
            while True:

                # receive mol_dict
                mol_dict = comm.recv(source=0, tag=2)
                # perform task
                if mol_dict is not None:
                    # init molecule
                    mol = gto.M(verbose = 0, output = None, unit = UNIT, \
                                basis = PARAMS['basis'], atom = mol_dict['struct'])
                    # decodense calc
                    res = decodense.main(mol, decomp)
                    # send results to master
                    comm.send({'idx': mol_dict['idx'], 'prop_nuc': res['prop_nuc'], 'prop_el': res['prop_el']}, dest=0, tag=1)
                else:
                    # exit
                    break

        # barrier
        comm.Barrier()

if __name__ == '__main__':
    main()


# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Functions for performing PSF fitting photometry on 2-D arrays."""

from __future__ import division
import warnings

import numpy as np
from astropy.modeling.parameters import Parameter
from astropy.utils.exceptions import AstropyUserWarning
from astropy.modeling.fitting import LevMarLSQFitter
from astropy.modeling import Fittable2DModel
from imageutils import (extract_array_2d, subpixel_indices, add_array_2d,
                        mask_to_mirrored_num)

__all__ = ['DiscretePRF', 'create_prf', 'psf_photometry',
           'GaussianPSF', 'subtract_psf']

class DiscretePRF(Fittable2DModel):
    """
    A discrete PRF model.

    The discrete PRF model stores images of the PRF at different subpixel
    positions or offsets as a lookup table. The resolution is given by the
    subsampling parameter, which states in how many subpixels a pixel is
    divided.

    The discrete PRF model class in initialized with a 4 dimensional
    array, that contains the PRF images at different subpixel positions.
    The definition of the axes is as following:

        1. Axis: y subpixel position
        2. Axis: x subpixel position
        3. Axis: y direction of the PRF image
        4. Axis: x direction of the PRF image

    The total array therefore has the following shape
    (subsampling, subsampling, prf_size, prf_size)

    Parameters
    ----------
    prf_array : ndarray
        Array containing PRF images.
    normalize : bool
        Normalize PRF images to unity.
    subsampling : int, optional
        Factor of subsampling. Default = 1.
    """
    amplitude = Parameter('amplitude')
    x_0 = Parameter('x_0')
    y_0 = Parameter('y_0')
    linear = True

    def __init__(self, prf_array, normalize=True, subsampling=1):

        # Array shape and dimension check
        if subsampling == 1:
            if prf_array.ndim == 2:
                prf_array = np.array([[prf_array]])
        if prf_array.ndim != 4:
            raise TypeError('Array must have 4 dimensions.')
        if prf_array.shape[:2] != (subsampling, subsampling):
            raise TypeError('Incompatible subsampling and array size')
        if np.isnan(prf_array).any():
            raise Exception("Array contains NaN values. Can't create PRF.")

        # Normalize if requested
        if normalize:
            for i in range(prf_array.shape[0]):
                for j in range(prf_array.shape[1]):
                    prf_array[i, j] /= prf_array[i, j].sum()

        # Set PRF asttributes
        self._prf_array = prf_array
        self.subsampling = subsampling

        constraints = {'fixed': {'x_0': True, 'y_0': True}}
        x_0 = 0
        y_0 = 0
        amplitude = 1
        super(DiscretePRF, self).__init__(n_models=1, x_0=x_0, y_0=y_0,
                                          amplitude=amplitude, **constraints)
        self.fitter = LevMarLSQFitter()

        # Fix position per default
        self.x_0.fixed = True
        self.y_0.fixed = True

    @property
    def shape(self):
        """
        Shape of the PRF image.
        """
        return self._prf_array.shape[-2:]

    def eval(self, x, y, amplitude, x_0, y_0):
        """
        Discrete PRF model evaluation.

        Given a certain position and amplitude the corresponding image of
        the PSF is chosen and scaled to the amplitude. If x and y are
        outside the boundaries of the image, zero will be returned.

        Parameters
        ----------
        x : float
            x coordinate array in pixel coordinates.
        y : float
            y coordinate array in pixel coordinates.
        amplitude : float
            Model amplitude.
        x_0 : float
            x position of the center of the PRF.
        y_0 : float
            y position of the center of the PRF.
        """
        # Convert x and y to index arrays
        x = (x - int(x_0 + 0.5)).astype('int') + self.shape[1] // 2
        y = (y - int(y_0 + 0.5)).astype('int') + self.shape[0] // 2

        # Get subpixel indices
        x_sub, y_sub = subpixel_indices((x_0, y_0), self.subsampling)

        # Out of boundary masks
        x_bound = np.logical_or(x < 0, x >= self.shape[1])
        y_bound = np.logical_or(y < 0, y >= self.shape[0])
        out_of_bounds = np.logical_or(x_bound, y_bound)

        # Set out of boundary indices to zero
        x[x_bound] = 0
        y[y_bound] = 0
        result = amplitude * self._prf_array[y_sub, x_sub][y, x]

        # Set out of boundary values to zero
        result[out_of_bounds] = 0
        return result

    def fit(self, data, indices):
        """
        Fit PSF/PRF to data.

        Fits the PSF/PRF to the data and returns the best fitting flux.
        If the data contains NaN values or if the source is not completely
        contained in the image data the fitting is omitted and a flux of 0
        is returned.

        For reasons of performance, indices for the data have to be created
        outside and passed to the function.

        The fit is performed on a slice of the data with the same size as
        the PRF.

        Parameters
        ----------
        data : ndarray
            Array containig image data.
        indices : ndarray
            Array with indices of the data. As
            returned by np.indices(data.shape)
        """
        # Extract sub array of the data of the size of the PRF grid
        sub_array_data = extract_array_2d(data, self.shape,
                                          (self.x_0.value, self.y_0.value))

        # Fit only if PSF is completely contained in the image and no NaN
        # values are present
        if sub_array_data.shape == self.shape and not np.isnan(sub_array_data).any():
            y = extract_array_2d(indices[0], self.shape,
                                 (self.x_0.value, self.y_0.value))
            x = extract_array_2d(indices[1], self.shape,
                                 (self.x_0.value, self.y_0.value))
            # TODO: It should be discussed whether this is  the right place to fix
            # the warning. Maybe it should be handled better in astropy.modeling.fitting
            with warnings.catch_warnings():
                warnings.simplefilter("ignore", AstropyUserWarning)
                m = self.fitter(self, x, y, sub_array_data)
            return m.amplitude.value
        else:
            return 0

class GaussianPSF(Fittable2DModel):
    """
    Symmetrical Gaussian PSF model.

    The PSF is evaluated by using the `scipy.special.erf` function
    on a fixed grid of the size of 1 pixel to assure flux conservation
    on subpixel scale.

    Parameters
    ----------
    sigma : float
        Width of the Gaussian PSF.
    amplitude : float (default 1)
        Amplitude at the peak value.
    x_0 : float (default 0)
        Position of the peak in x direction.
    y_0 : float (default 0)
        Position of the peak in y direction.

    Notes
    -----
    The PSF model is evaluated according to the following formula:

        .. math::

            f(x, y) =
                \\frac{A}{4}
                \\left[
                \\textnormal{erf} \\left(\\frac{x - x_0 + 0.5}
                {\\sqrt{2} \\sigma} \\right) -
                \\textnormal{erf} \\left(\\frac{x - x_0 - 0.5}
                {\\sqrt{2} \\sigma} \\right)
                \\right]
                \\left[
                \\textnormal{erf} \\left(\\frac{y - y_0 + 0.5}
                {\\sqrt{2} \\sigma} \\right) -
                \\textnormal{erf} \\left(\\frac{y - y_0 - 0.5}
                {\\sqrt{2} \\sigma} \\right)
                \\right]

    Where ``erf`` denotes the error function.
    """
    amplitude = Parameter('amplitude')
    x_0 = Parameter('x_0')
    y_0 = Parameter('y_0')
    sigma = Parameter('sigma')

    _erf = None

    def __init__(self, sigma, amplitude=1, x_0=0, y_0=0):
        if self._erf is None:
            from scipy.special import erf
            self.__class__._erf = erf

        constraints = {'fixed': {'x_0': True, 'y_0': True, 'sigma': True}}
        super(GaussianPSF, self).__init__(n_models=1, sigma=sigma,
                                          x_0=x_0, y_0=y_0,
                                          amplitude=amplitude, **constraints)

        # Default size is 8 * sigma
        self.shape = (int(8 * sigma) + 1, int(8 * sigma) + 1)
        self.fitter = LevMarLSQFitter()

        # Fix position per default
        self.x_0.fixed = True
        self.y_0.fixed = True

    def eval(self, x, y, amplitude, x_0, y_0, sigma):
        """
        Model function Gaussian PSF model.
        """
        return amplitude / 4 * ((self._erf((x - x_0 + 0.5) / (np.sqrt(2) * sigma))
                            - self._erf((x - x_0 - 0.5) / (np.sqrt(2) * sigma)))
                            * (self._erf((y - y_0 + 0.5) / (np.sqrt(2) * sigma))
                            - self._erf((y - y_0 - 0.5) / (np.sqrt(2) * sigma))))

    def fit(self, data, indices):
        """
        Fit PSF/PRF to data.

        Fits the PSF/PRF to the data and returns the best fitting flux.
        If the data contains NaN values or if the source is not completely
        contained in the image data the fitting is omitted and a flux of 0
        is returned.

        For reasons of performance, indices for the data have to be created
        outside and passed to the function.

        The fit is performed on a slice of the data with the same size as
        the PRF.

        Parameters
        ----------
        data : ndarray
            Array containig image data.
        indices : ndarray
            Array with indices of the data. As
            returned by np.indices(data.shape)

        Returns
        -------
        flux : float
            Best fit flux value. Returns flux = 0 if PSF is not completely
            contained in the image or if NaN values are present.
        """
        # Set position
        position = (self.x_0.value, self.y_0.value)

        # Extract sub array with data of interest
        sub_array_data = extract_array_2d(data, self.shape, position)

        # Fit only if PSF is completely contained in the image and no NaN
        # values are present
        if sub_array_data.shape == self.shape and not np.isnan(sub_array_data).any():
            y = extract_array_2d(indices[0], self.shape, position)
            x = extract_array_2d(indices[1], self.shape, position)
            m = self.fitter(self, x, y, sub_array_data)
            return m.amplitude.value
        else:
            return 0

def psf_photometry(data, positions, psf, mask=None, mode='sequential',
                   tune_coordinates=False):
    """
    Perform PSF/PRF photometry on the data.

    Given a PSF or PRF model, the model is fitted simultaneously or
    sequentially to the given positions to obtain an estimate of the
    flux. If required, coordinates are also tuned to match best the data.

    If the data contains NaN values or the PSF/PRF is not completely
    contained in the image, a flux of zero is returned.

    Parameters
    ----------
    data : ndarray
        Image data array
    positions : List or array
        List of positions in pixel coordinates
        where to fit the PSF/PRF.
    psf : `photutils.psf.DiscretePRF` or `photutils.psf.GaussianPSF`
        PSF/PRF model to fit to the data.
    mask : ndarray, optional
        Mask to be applied to the data.
    mode : {'sequential', 'simultaneous'}
        One of the following modes to do PSF/PRF photometry:
            * 'simultaneous'
                Fit PSF/PRF simultaneous to all given positions.
            * 'sequential' (default)
                Fit PSF/PRF one after another to the given positions .

    Examples
    --------
    See `Spitzer PSF Photometry <http://nbviewer.ipython.org/gist/adonath/
    6550989/PSFPhotometrySpitzer.ipynb>`_ for a short tutorial.
    """
    # Check input array type and dimension.
    if np.iscomplexobj(data):
        raise TypeError('Complex type not supported')
    if data.ndim != 2:
        raise ValueError('{0}-d array not supported. '
                         'Only 2-d arrays supported.'.format(data.ndim))

    # Fit coordinates if requested
    if tune_coordinates:
        psf.fixed['x_0'] = False
        psf.fixed['y_0'] = False

    # Actual photometry
    result = np.array([])
    indices = np.indices(data.shape)

    if mode == 'simultaneous':
        raise NotImplementedError('Simultaneous mode not implemented')
    elif mode == 'sequential':
        for i, position in enumerate(positions):
                psf.x_0, psf.y_0 = position
                flux = psf.fit(data, indices)
                result = np.append(result, flux)
    else:
        raise Exception('Invalid photometry mode.')
    return result

def create_prf(data, positions, size, fluxes=None, mask=None, mode='mean',
               subsampling=1, fix_nan=False):
    """
    Estimate point response function (PRF) from image data.

    Given a list of positions and size this function estimates an image of
    the PRF by extracting and combining the individual PRFs from the given
    positions. Different modes of combining are available.

    NaN values are either ignored by passing a mask or can be replaced by
    the mirrored value with respect to the center of the PRF.

    Furthermore it is possible to specify fluxes to have a correct
    normalization of the individual PRFs. Otherwise the flux is estimated from
    a quadratic aperture of the same size as the PRF image.

    Parameters
    ----------
    data : array
        Data array
    positions : List or array
        List of pixel coordinate source positions to use in creating the PRF.
    size : odd int
        Size of the quadratic PRF image in pixels.
    mask : bool array, optional
        Boolean array to mask out bad values.
    fluxes : array, optional
        Object fluxes to normalize extracted PRFs.
    mode : {'mean', 'median'}
        One of the following modes to combine the extracted PRFs:
            * 'mean'
                Take the pixelwise mean of the extracted PRFs.
            * 'median'
                Take the pixelwise median of the extracted PRFs.
    subsampling : int
        Factor of subsampling of the PRF (default = 1).
    fix_nan : bool
        Fix NaN values in the data by replacing it with the
        mirrored value. Assuming that the PRF is symmetrical.

    Returns
    -------
    prf : `photutils.psf.DiscretePRF`
        Discrete PRF model estimated from data.

    Notes
    -----
    In Astronomy different definitions of Point Spread Function (PSF) and
    Point Response Function (PRF) are used. Here we assume that the PRF is
    an image of a point source after discretization e.g. with a CCD. This
    definition is equivalent to the `Spitzer definiton of the PRF
    <http://irsa.ipac.caltech.edu/data/SPITZER/docs/dataanalysistools/tools/mopex/mopexusersguide/89/>`_.

    References
    ----------
    `Spitzer PSF vs. PRF
    <http://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/PRF_vs_PSF.pdf>`_

    `Kepler PSF calibration
    <http://keplerscience.arc.nasa.gov/CalibrationPSF.shtml>`_

    `The Kepler Pixel Response Function
    <http://adsabs.harvard.edu/abs/2010ApJ...713L..97B>`_
    """

    # Check input array type and dimension.
    if np.iscomplexobj(data):
        raise TypeError('Complex type not supported')
    if data.ndim != 2:
        raise ValueError('{0}-d array not supported. '
                         'Only 2-d arrays supported.'.format(data.ndim))
    if size % 2 == 0:
        raise TypeError("Size must be odd.")

    if fluxes is not None and len(fluxes) != len(positions):
        raise TypeError("Position and flux arrays must be of equal length.")

    if mask is None:
        mask = np.isnan(data)

    if isinstance(positions, (list, tuple)):
        positions = np.array(positions)

    if isinstance(fluxes, (list, tuple)):
        fluxes = np.array(fluxes)

    if mode == 'mean':
        combine = np.ma.mean
    elif mode == 'median':
        combine = np.ma.median
    else:
        raise Exception('Invalid mode to combine prfs.')

    data_internal = np.ma.array(data=data, mask=mask)
    prf_model = np.ndarray(shape=(subsampling, subsampling, size, size))
    positions_subpixel_indices = np.array([subpixel_indices(_, subsampling)
                                           for _ in positions])

    for i in range(subsampling):
        for j in range(subsampling):
            extracted_sub_prfs = []
            sub_prf_indices = np.all(positions_subpixel_indices == [j, i],
                                     axis=1)
            positions_sub_prfs = positions[sub_prf_indices]
            for k, position in enumerate(positions_sub_prfs):
                extracted_prf = extract_array_2d(data_internal, (size, size),
                                                 position)
                # Check shape to exclude incomplete PRFs at the boundaries
                # of the image
                if extracted_prf.shape == (size, size) and np.ma.sum(extracted_prf) != 0:
                    # Replace NaN values by mirrored value, with respect
                    # to the prf's center
                    if fix_nan:
                        prf_nan = np.isnan(extracted_prf)
                        if prf_nan.any():
                            if prf_nan.sum() > 3 or prf_nan[size / 2, size / 2]:
                                continue
                            else:
                                extracted_prf = mask_to_mirrored_num(
                                    extracted_prf, prf_nan)
                    # Normalize and add extracted PRF to data cube
                    if fluxes is None:
                        extracted_prf_norm = np.ma.copy(extracted_prf) / np.ma.sum(extracted_prf)
                    else:
                        fluxes_sub_prfs = fluxes[sub_prf_indices]
                        extracted_prf_norm = np.ma.copy(extracted_prf) / fluxes_sub_prfs[k]
                    extracted_sub_prfs.append(extracted_prf_norm)
                else:
                    continue
            prf_model[i, j] = np.ma.getdata(combine(np.ma.dstack(extracted_sub_prfs), axis=2))
    return DiscretePRF(prf_model, subsampling=subsampling)

def subtract_psf(data, psf, positions, fluxes, mask=None):
    """
    Removes PSF/PRF at the given positions.

    To calculate residual images the PSF/PRF model is subtracted from the data
    at the given positions.

    Parameters
    ----------
    data : ndarray
        Image data.
    psf : `photutils.psf.DiscretePRF` or `photutils.psf.GaussianPSF`
        PSF/PRF model to be substracted from the data.
    positions : ndarray
        List of center positions where PSF/PRF is removed.
    fluxes : ndarray
        List of fluxes of the sources, for correct
        normalization.
    """
    # Set up indices
    indices = np.indices(data.shape)

    # Loop over position
    for i, position in enumerate(positions):
        y = extract_array_2d(indices[0], psf.shape, position)
        x = extract_array_2d(indices[1], psf.shape, position)
        psf_image = psf.eval(x, y, fluxes[i], position[0], position[1])
        data = add_array_2d(data, -psf_image, position)
    return data


import numpy
import math
import sys
import conf
#COMMONINPT
# NGAS=conf.NGAS
# NSTEP=conf.NSTEP
# NANISO=conf.NANISO
# EFINAL=conf.EFINAL
# ESTEP=conf.ESTEP
# AKT=conf.AKT
# ARY=conf.ARY
# TEMPC=conf.TEMPC
# TORR=conf.TORR
# IPEN=conf.IPEN
# #COMMONCNSTS
# ECHARG=conf.ECHARG
# EMASS=conf.EMASS
# AMU=conf.AMU
# PIR2=conf.PIR2
# #COMMONINPT2
# KGAS=conf.KGAS
# LGAS=conf.LGAS
# DETEFF=conf.DETEFF
# EXCWGHT=conf.EXCWGHT
# #COMMONINPT1
# NDVEC=conf.NDVEC
# #COMMONCNSTS1
# CONST1=conf.CONST1
# CONST2=conf.CONST2
# CONST3=conf.CONST3
# CONST4=conf.CONST4
# CONST5=conf.CONST5
# #COMMONRATIO
# AN1=conf.AN1
# AN2=conf.AN2
# AN3=conf.AN3
# AN4=conf.AN4
# AN5=conf.AN5
# AN6=conf.AN6
# AN=conf.AN
# FRAC=conf.FRAC
# #COMMONGASN
# NGASN=conf.NGASN
# #COMMONSETP
# TMAX=conf.TMAX
# SMALL=conf.SMALL
# API=conf.API
# ESTART=conf.ESTART
# THETA=conf.THETA
# PHI=conf.PHI
# TCFMAX=conf.TCFMAX
# TCFMAX1=conf.TCFMAX1

# RSTART=conf.RSTART
# EFIELD=conf.EFIELD
# ETHRM=conf.ETHRM
# ECUT=conf.ECUT
# NEVENT=conf.NEVENT
# IMIP=conf.IMIP
# IWRITE=conf.IWRITE
# #COMMONSET2
# DRXINIT=conf.DRXINIT
# DRYINIT=conf.DRYINIT
# DRZINIT=conf.DRZINIT
# #COMMONBFLD
# EOVB=conf.EOVB
# WB=conf.WB
# BTHETA=conf.BTHETA
# BMAG=conf.BMAG
# #COMMONIONC
# DOUBLE=conf.DOUBLE
# CMINIXSC=conf.CMINIXSC
# CMINEXSC=conf.CMINEXSC
# ECLOSS=conf.ECLOSS

# WPLN=conf.WPLN
# ICOUNT=conf.ICOUNT
# AVPFRAC=conf.AVPFRAC
# #COMMONMRATIO
# VAN1=conf.VAN1
# VAN2=conf.VAN2
# VAN3=conf.VAN3
# VAN4=conf.VAN4
# VAN5=conf.VAN5
# VAN6=conf.VAN6
# VAN=conf.VAN
# #COMMONOUTPT
# ICOLL=conf.ICOLL
# NETOT=conf.NETOT
# NPRIME=conf.NPRIME
# TMAX1=conf.TMAX1
# TIME=conf.TIME
# NNULL=conf.NNULL

# NITOT=conf.NITOT
# ICOLN=conf.ICOLN
# ICOLNN=conf.ICOLNN
# NREAL=conf.NREAL
# NEXCTOT=conf.NEXCTOT
# #COMMONPRIM3
# MSUM=conf.MSUM
# MCOMP=conf.MCOMP
# MRAYL=conf.MRAYL
# MPAIR=conf.MPAIR

# MPHOT=conf.MPHOT
# MVAC=conf.MVAC
# #COMMONRLTVY
# BET=conf.BET
# GAM=conf.GAM
# VC=conf.VC
# EMS=conf.EMS
# #COMMONCOMP
# ICMP=conf.ICMP
# ICFLG=conf.ICFLG
# IRAY=conf.IRAY
# IRFLG=conf.IRFLG
# IPAP=conf.IPAP
# IPFLG=conf.IPFLG
# IBRM=conf.IBRM
# IBFLG=conf.IBFLG
# LPEFLG=conf.LPEFLG
# #COMMONMIX2
# E=conf.E
# EROOT=conf.EROOT
# QTOT=conf.QTOT
# QREL=conf.QREL

# QINEL=conf.QINEL
# QEL=conf.QEL
# #COMMONPLOT
# NXPL10=conf.NXPL10
# NYPL10=conf.NYPL10
# NZPL10=conf.NZPL10
# NXPL40=conf.NXPL40

# NYPL40=conf.NYPL40
# NZPL40=conf.NZPL40
# NXPL100=conf.NXPL100
# NYPL100=conf.NYPL100
# NZPL100=conf.NZPL100

# NXPL400=conf.NXPL400
# NYPL400=conf.NYPL400
# NZPL400=conf.NZPL400
# NXPL1000=conf.NXPL1000
# NYPL1000=conf.NYPL1000

# NZPL1000=conf.NZPL1000
# NXPL2=conf.NXPL2
# NYPL2=conf.NYPL2
# NZPL2=conf.NZPL2
# NXPL4000=conf.NXPL4000

# NYPL4000=conf.NYPL4000
# NZPL4000=conf.NZPL4000
# NXPL10000=conf.NXPL10000
# NYPL10000=conf.NYPL10000

# NZPL10000=conf.NZPL10000
# NXPL40000=conf.NXPL40000
# NYPL40000=conf.NYPL40000
# NZPL40000=conf.NZPL40000

# NXPL100000=conf.NXPL100000
# NYPL100000=conf.NYPL100000
# NZPL100000=conf.NZPL100000
# NRPL2=conf.NRPL2
# NRPL10=conf.NRPL10

# NRPL40=conf.NRPL40
# NRPL100=conf.NRPL100
# NRPL400=conf.NRPL400
# NRPL1000=conf.NRPL1000
# NRPL4000=conf.NRPL4000

# NRPL10000=conf.NRPL10000
# NRPL40000=conf.NRPL40000
# NRPL100000=conf.NRPL100000
# NEPL1=conf.NEPL1

# NEPL10=conf.NEPL10
# NEPL100=conf.NEPL100
# MELEC=conf.MELEC
# MELEC3=conf.MELEC3
# MELEC10=conf.MELEC10

# MELEC30=conf.MELEC30
# MELEC100=conf.MELEC100
# MELEC300=conf.MELEC300
# #COMMONBREMG
# EBRGAM=conf.EBRGAM
# BRDCOSX=conf.BRDCOSX
# BRDCOSY=conf.BRDCOSY
# BRDCOSZ=conf.BRDCOSZ

# BRX=conf.BRX
# BRY=conf.BRY
# BRZ=conf.BRZ
# BRT=conf.BRT
# EBRTOT=conf.EBRTOT
# NBREM=conf.NBREM
# #COMMONCLUS
# XAV=conf.XAV
# YAV=conf.YAV
# ZAV=conf.ZAV
# TAV=conf.TAV

# XYAV=conf.XYAV
# XYZAV=conf.XYZAV
# DX=conf.DX
# DY=conf.DY
# DZ=conf.DZ

# DT=conf.DT
# DXY=conf.DXY
# DXYZ=conf.DXYZ
# NCL=conf.NCL
# FARX1=conf.FARX1
# FARY1=conf.FARY1
# FARZ1=conf.FARZ1
# FARXY1=conf.FARXY1
# RMAX1=conf.RMAX1

# TSUM=conf.TSUM
# XNEG=conf.XNEG

# YNEG=conf.YNEG
# ZNEG=conf.ZNEG
# EDELTA=conf.EDELTA
# EDELTA2=conf.EDELTA2

# NCLEXC=conf.NCLEXC
# #COMMONKSEED
# NSEED=conf.NSEED
# #COMMONECASC
# NEGAS=conf.NEGAS
# LEGAS=conf.LEGAS
# IESHELL=conf.IESHELL
# IECASC=conf.IECASC

def SETUP(LAST):
	def GOTO999():
		# print("in GOTO999")
		print(conf.NGASN,conf.FRAC)
		print(' ERROR IN GAS INPUT : NGAS=',conf.NGAS,'\n')
		for J in range(1,6+1):
			# print(J)
			print(' N=',J,' NGAS=',conf.NGASN[J],' FRAC=',conf.FRAC[J])
		LAST=1
		return LAST
	#IMPLICIT #real*8 (A-H,O-Z)
	#IMPLICIT #integer*8 (I-N)
	#integer*4 NSEED
	global NGAS,NSTEP,NANISO,EFINAL,ESTEP,AKT,ARY,TEMPC,TORR,IPEN
	global ECHARG,EMASS,AMU,PIR2
	global KGAS,LGAS,DETEFF,EXCWGHT
	global NDVEC,CONST1,CONST2,CONST3,CONST4,CONST5
	global AN1,AN2,AN3,AN4,AN5,AN6,AN,FRAC #=[0 for x in range[6]]
	global NGASN #=[0 for x in range[6]]
	global TMAX,SMALL,API,ESTART,THETA,PHI,TCFMAX #=[0 for x in range(10)]
	global TCFMAX1,RSTART,EFIELD,ETHRM,ECUT,NEVENT,IMIP,IWRITE
	global DRXINIT,DRYINIT,DRZINIT
	global EOVB,WB,BTHETA,BMAG
	global DOUBLE #=[[0 for x in range[6]] for y in range(20000)]
	global AVPFRAC #=[[0 for x in range(3)] for y in range(6)]
	global CMINIXSC #=[0 for x in range[6]]
	global CMINEXSC #=[0 for x in range[6]]
	global ECLOSS #=[0 for x in range[6]]
	global WPLN #=[0 for x in range[6]]
	global ICOUNT
	global OVAN1,VAN2,VAN3,VAN4,VAN5,VAN6,VAN
	global ICOLL#=[0 for x in range(30)]
	global NETOT,NPRIME,TMAX1
	global TIME #=[0 for x in range(300)]
	global NNULL,NITOT
	global ICOLN #=[0 for x i range(512)]
	global ICOLNN#=[0 for x in range(60)]
	global NREAL,NEXCTOT
	global MSUM#=[0 for x in range(10000)]
	global MCOMP#=[0 for x in range(10000)]
	global MRAYL#=[0 for x in range(10000)]
	global MPAIR#=[0 for x in range(10000)]
	global MPHOT#=[0 for x in range(10000)]
	global MVAC#=[0 for x in range(10000)]
	global BET#=[0 for x in range(2000)]
	global GAM#=[0 for x in range(20000)]
	global VC,EMS
	global ICMP,ICFLG,IRAY,IRFLG,IPAP,IPFLG,IBRM,IBFLG,LPEFLG
	global E #=[0 for x in range(20000)]
	global EROOT #=[0 for x in range(20000)]
	global QTOT #=[0 for x in range(20000)]
	global QREL #=[0 for x in range(20000)]
	global QINEL #=[0 for x in range(20000)]
	global QEL #=[0 for x in range(20000)]
	global NXPL10#=[0 for x in range(31)]
	global NYPL10#=[0 for x in range(31)]
	global NZPL10#=[0 for x in range(31)]
	global NXPL40#=[0 for x in range(31)]
	global NYPL40#=[0 for x in range(31)]
	global NZPL40#=[0 for x in range(31)]
	global NXPL100#=[0 for x in range(31)]
	global NYPL100#=[0 for x in range(31)]
	global NZPL100#=[0 for x in range(31)]
	global NXPL400#=[0 for x in range(31)]
	global NYPL400#=[0 for x in range(31)]
	global NZPL400#=[0 for x in range(31)]
	global NXPL1000#=[0 for x in range(31)]
	global NYPL1000#=[0 for x in range(31)]
	global NZPL1000#=[0 for x in range(31)]
	global NXPL2#=[0 for x in range(31)]
	global NYPL2#=[0 for x in range(31)]
	global NZPL2#=[0 for x in range(31)]
	global NXPL4000#=[0 for x in range(31)]
	global NYPL4000#=[0 for x in range(31)]
	global NZPL4000#=[0 for x in range(31)]
	global NXPL10000#=[0 for x in range(31)]
	global NYPL10000#=[0 for x in range(31)]
	global NZPL10000#=[0 for x in range(31)]
	global NXPL40000#=[0 for x in range(31)]
	global NYPL40000#=[0 for x in range(31)]
	global NZPL40000#=[0 for x in range(31)]
	global NXPL100000#=[0 for x in range(31)]
	global NYPL100000#=[0 for x in range(31)]
	global NZPL100000#=[0 for x in range(31)]
	global NRPL2#=[0 for x in range(31)]
	global NRPL10#=[0 for x in range(31)]
	global NRPL40#=[0 for x in range(31)]
	global NRPL100#=[0 for x in range(31)]
	global NRPL400#=[0 for x in range(31)]
	global NRPL1000#=[0 for x in range(31)]
	global NRPL4000#=[0 for x in range(31)]
	global NRPL10000#=[0 for x in range(31)]
	global NRPL40000#=[0 for x in range(31)]
	global NRPL100000#=[0 for x in range(31)]
	global NEPL1#=[0 for x in range(100)]
	global NEPL10#=[0 for x in range(100)]
	global NEPL100#=[0 for x in range(100)]
	global MELEC#=[0 for x in range(1000)]
	global MELEC3#=[0 for x in range(1000)]
	global MELEC10#=[0 for x in range(1000)]
	global MELEC30#=[0 for x in range(1000)]
	global MELEC100#=[0 for x in range(1000)]
	global MELEC300#=[0 for x in range(1000)]
	global EBRGAM#=[0 for x in range(10)]
	global BRDCOSX# =[0 for x in range(10)]
	global BRDCOSY# =[0 for x in range(10)]
	global BRDCOSZ# =[0 for x in range(10)]
	global BRX#=[0 for x in range(10)]
	global BRY#=[0 for x in range(10)]
	global BRZ#=[0 for x in range(10)]
	global BRT#=[0 for x in range(10)]
	global EBRTOT#=[0 for x in range[6]]
	global NBREM#=[0 for x in range[6]]
	global XAV#=[0 for x in range(100000)]
	global YAV#=[0 for x in range(100000)]
	global ZAV#=[0 for x in range(100000)]
	global TAV#=[0 for x in range(100000)]
	global XYAV#=[0 for x in range(100000)]
	global XYZAV#=[0 for x in range(100000)]
	global DX#=[0 for x in range(100000)]
	global DY#=[0 for x in range(100000)]
	global DZ#=[0 for x in range(100000)]
	global DT#=[0 for x in range(100000)]
	global DXY#=[0 for x in range(100000)]
	global DXYZ#=[0 for x in range(100000)]
	global NCL#=[0 for x in range(100000)]
	global FARX1#=[0 for x in range(100000)]
	global FARY1#=[0 for x in range(100000)]
	global FARZ1#=[0 for x in range(100000)]
	global FARXY1#=[0 for x in range(100000)]
	global RMAX1#=[0 for x in range(100000)]
	global TSUM#=[0 for x in range(100000)]
	global XNEG#=[0 for x in range(100000)]
	global YNEG#=[0 for x in range(100000)]
	global ZNEG#=[0 for x in range(100000)]
	global EDELTA#[100000]
	global EDELTA2#=[0 for x in range(100000)]
	global NCLEXC#=[0 for x in range(100000)]
	global NSEED
	global NEGAS#=[0 for x in range(512)]
	global LEGAS#=[0 for x in range(512)]
	global IESHELL#=[0 for x in range(512)]
	global IECASC
	#
	#   NEW UPDATE OF CONSTANTS 2010
	#
	conf.API=numpy.arccos(-1.00)
	conf.ARY=13.605692530
	conf.PIR2=8.7973554297*(10**-17)
	conf.ECHARG=1.602176565*(10**-19)
	conf.EMASS=9.10938291*(10**-31)
	conf.EMS=510998.9280
	conf.VC=299792458.00
	conf.AMU=1.660538921*(10**-27)
	BOLTZ=8.6173324*(10**-5)
	BOLTZJ=1.3806488*(10**-23)
	AWB=1.758820088*(10**10)
	ALOSCH=2.6867805*(10**19)
	RE=2.8179403267*(10**-13)
	ALPH=137.035999074
	HBAR=6.58211928*(10**-16)
	EOVM=math.sqrt(2.00*conf.ECHARG/conf.EMASS)*100.00
	ABZERO=273.150
	ATMOS=760.00
	conf.CONST1=AWB/2.00*1.0*(10**-19)
	conf.CONST2=conf.CONST1*1.0*(10**-2)
	conf.CONST3=math.sqrt(0.20*AWB)*1.0*(10**-9)
	conf.CONST4=conf.CONST3*ALOSCH*1.0*(10**-15)
	conf.CONST5=conf.CONST3/2.00
	TWOPI=2.00*conf.API
	conf.NANISO=2
	conf.NBREM=numpy.zeros(7)  # negotiated for extra element
	conf.EBRTOT=numpy.zeros(7)   # negotiated for extra element

	conf.ICFLG=0
	conf.IRFLG=0
	conf.IPFLG=0
	conf.IBFLG=0
	conf.LPEFLG=0
	#  --------------------------------------------
	#
	#      READ IN OUTPUT CONTROL AND INTEGRATION DATA
	#
	conf.NGAS,conf.NEVENT,conf.IMIP,conf.NDVEC,conf.NSEED,conf.ESTART,conf.ETHRM,conf.ECUT=2,100,5,1,0,1.0,1.5,2.0

	# NGAS,NEVENT,IMIP,NDVEC,NSEED,ESTART,ETHRM,ECUT=input("Input Card 1 ").split()
	conf.NGAS=int(conf.NGAS)#input('NGAS'))
	conf.NEVENT=int(conf.NEVENT)#input('NEVENT'))
	conf.IMIP=int(conf.IMIP)#input('IMIP'))
	conf.NDVEC=int(conf.NDVEC)#input('NDVEC'))
	conf.NSEED=int(conf.NSEED)#input('NSEED'))
	conf.ESTART=float(conf.ESTART)#input('ESTART'))
	conf.ETHRM=float(conf.ETHRM)#input('ETHRM'))
	conf.ECUT=float(conf.ECUT)#input('ECUT'))
	conf.ICOUNT=0

	if(conf.IMIP == 1):
		conf.ICOUNT=1
	if(conf.NGAS == 0):
		LAST=1
		return LAST
	if(conf.ESTART > 3.0*(10**6) and conf.IMIP == 3):
		print(' SUBROUTINE STOPPED: X-RAY ENERGY=','%.3f' % conf.ESTART,'EV. MAXIMUM ENERGY 3.0MEV')
		sys.exit()
	# endif
	if(conf.IMIP != 1 and conf.NEVENT > 10000):
		print(' SUBROUTINE STOPPED: NUMBER OF EVENTS =',conf.NEVENT,' LARGER THAN ARRAY LIMIT OF 10000')
		sys.exit()
	# endif
	if(conf.IMIP == 1 and conf.NEVENT > 100000):
		print(' SUBROUTINE STOPPED: NUMBER OF EVENTS =',conf.NEVENT,' LARGER THAN ARRAY LIMIT OF 100000')
		sys.exit()
	# endif
	#
	#   GAS IDENTIFIERS
	#
	conf.NGASN=numpy.zeros(7)
	card2=[2 , 12 , 0 , 0 , 0 , 0]
	# card2=input("Input Card 2 ").split()
	for i in range(6):
		conf.NGASN[i+1]=card2[i]
	#
	#      GAS PARAMETERS
	#
	conf.FRAC=numpy.zeros(7)
	card3=[80.000,20.000,0.0,0.0,0.0,0.0,20.000,760.000]
	# card3=input("Input Card 3 ").split()
	for i in range(6):
		conf.FRAC[i+1]=float(card3[i])
	conf.TEMPC=round(float(card3[6]),4)  				#print(8'%.4f' %)
	conf.TORR=round(float(card3[7]),4)                  	#print(8'%.4f' %)

	#print(8'%.4f' %)
	#
	#      FIELD VALUES
	#
	conf.EFIELD,conf.BMAG,conf.BTHETA,conf.IWRITE,conf.IPEN=2.000,3.000,30.000,0,0
	# EFIELD,BMAG,BTHETA,IWRITE,IPEN=input("Input Card 4 ").split()
	conf.EFIELD=round(float(conf.EFIELD),3)  			#print(3'%.3f' % ,2I5)
	conf.BMAG=round(float(conf.BMAG),3)			#print(3'%.3f' % ,2I5)
	conf.BTHETA=round(float(conf.BTHETA),3)			#print(3'%.3f' % ,2I5)
	conf.IWRITE=int(conf.IWRITE)			#print(3'%.3f' % ,2I5)
	conf.IPEN=int(conf.IPEN)                    			#print(3'%.3f' % ,2I5)

	conf.DETEFF,conf.EXCWGHT,conf.KGAS,conf.LGAS,conf.ICMP,conf.IRAY,conf.IPAP,conf.IBRM,conf.IECASC=50.0,0.55,2,1,1,1,1,1,1
	# DETEFF,EXCWGHT,KGAS,LGAS,ICMP,IRAY,IPAP,IBRM,IECASC=input("Input Card 5 ").split()
	conf.DETEFF=round(float(conf.DETEFF),3)      	# print(2'%.3f' % ,7I5)
	conf.EXCWGHT=round(float(conf.EXCWGHT),3)		# print(2'%.3f' % ,7I5)
	conf.KGAS=int(conf.KGAS)						# print(2'%.3f' % ,7I5)
	conf.LGAS=int(conf.LGAS)						# print(2'%.3f' % ,7I5)
	conf.ICMP=int(conf.ICMP)						# print(2'%.3f' % ,7I5)
	conf.IRAY=int(conf.IRAY)						# print(2'%.3f' % ,7I5)
	conf.IPAP=int(conf.IPAP)						# print(2'%.3f' % ,7I5)
	conf.IBRM=int(conf.IBRM)						# print(2'%.3f' % ,7I5)
	conf.IECASC =int(conf.IECASC)					# print(2'%.3f' % ,7I5)
	#     WRITE(6,656) IWRITE
	# 656 print(' IWRITE=',I3)
	if(conf.IMIP != 0):
		outputfile=open("DEGRAD.OUT","w")
	# CALCULATE EFINAL FOR DELTAS OR XRAYS
	# INCREASED EFINAL CAUSED BY ELECTRIC FIELD
	EBIG=0.05*conf.ESTART/1000.
	conf.EFINAL=conf.ESTART*1.0001+760.0*EBIG/conf.TORR*(conf.TEMPC+ABZERO)/293.15*conf.EFIELD
	if(conf.EFINAL < (1.01*conf.ESTART)):
		conf.EFINAL=1.01*conf.ESTART
	#   CHECK INPUT
	TOTFRAC=0.00
	if(conf.NGAS == 0 or conf.NGAS > 6):
			GOTO999()
	for J in range(1,conf.NGAS+1):
		# 	print('J',J)
		if(conf.NGASN[J]== 0 or conf.FRAC[J] == 0.00):
			GOTO999()
		TOTFRAC=TOTFRAC+conf.FRAC[J]

	if(abs(TOTFRAC-100.00)> 1*(10**-6)):
		print(TOTFRAC)
		GOTO999()
	LAST=0
	conf.TMAX=100.00
	NOUT=10
	conf.NSTEP=20000
	# INITIAL ANGLES
	if(conf.NDVEC): #22594
		conf.PHI=0
		conf.THETA=0
	elif(conf.NDVEC==-1):
		conf.PHI=0
		conf.THETA=numpy.arccos(-1)
	elif(conf.NDVEC==0):
		conf.PHI=0.0
		conf.THETA=conf.API/2.0
	elif(conf.NDVEC==2):
		R3=DRAND48(0.0,1.0)
		conf.PHI=TWOPI*R3
		R4=DRAND48(1.5, 1.9)
		conf.THETA=numpy.arccos(1.0-2.0*R4)
	else :
		print('DIRECTION OF BEAM NOT DEFINED NDVEC =',conf.NDVEC)
		sys.exit()

	# INITIAL DIRECTION COSINES FOR CASCADE CALCULATION
	conf.DRZINIT= numpy.cos(conf.THETA)
	conf.DRXINIT= numpy.sin(conf.THETA)*numpy.cos(conf.PHI)
	conf.DRYINIT=numpy.sin(conf.THETA)*numpy.sin(conf.PHI)
	# ZERO COMMON BLOCKS OF OUTPUT RESULTS
	conf.MSUM=numpy.zeros(10001,dtype=int)
	conf.MCOMP=numpy.zeros(10001,dtype=int)
	conf.MRAYL=numpy.zeros(10001,dtype=int)
	conf.MPAIR=numpy.zeros(10001,dtype=int)
	conf.MPHOT=numpy.zeros(10001,dtype=int)
	conf.MVAC=numpy.zeros(10001,dtype=int)

	# for J in range(1,300):
	conf.TIME=numpy.zeros(301,dtype=int)
	# for K in range(1,30):
	conf.ICOLL=numpy.zeros(31,dtype=int)
	# for K in range(1,512):
	conf.ICOLN=numpy.zeros(513,dtype=int)
	# for K in range(1,60):
	conf.ICOLNN=numpy.zeros(61,dtype=int)
	# for K in range(1,10):
	conf.TCFMAX=numpy.zeros(11)
	# ZERO PLOT ARRAYS
	conf.NXPL2=numpy.zeros(32,dtype=int)
	conf.NYPL2=numpy.zeros(32,dtype=int)
	conf.NZPL2=numpy.zeros(32,dtype=int)
	conf.NXPL10=numpy.zeros(32,dtype=int)
	conf.NYPL10=numpy.zeros(32,dtype=int)
	conf.NZPL10=numpy.zeros(32,dtype=int)
	conf.NXPL40=numpy.zeros(32,dtype=int)
	conf.NYPL40=numpy.zeros(32,dtype=int)
	conf.NZPL40=numpy.zeros(32,dtype=int)
	conf.NXPL100=numpy.zeros(32,dtype=int)
	conf.NYPL100=numpy.zeros(32,dtype=int)
	conf.NZPL100=numpy.zeros(32,dtype=int)
	conf.NXPL400=numpy.zeros(32,dtype=int)
	conf.NYPL400=numpy.zeros(32,dtype=int)
	conf.NZPL400=numpy.zeros(32,dtype=int)
	conf.NXPL1000=numpy.zeros(32,dtype=int)
	conf.NYPL1000=numpy.zeros(32,dtype=int)
	conf.NZPL1000=numpy.zeros(32,dtype=int)
	conf.NXPL4000=numpy.zeros(32,dtype=int)
	conf.NYPL4000=numpy.zeros(32,dtype=int)
	conf.NZPL4000=numpy.zeros(32,dtype=int)
	conf.NXPL10000=numpy.zeros(32,dtype=int)
	conf.NYPL10000=numpy.zeros(32,dtype=int)
	conf.NZPL10000=numpy.zeros(32,dtype=int)
	conf.NXPL40000=numpy.zeros(32,dtype=int)
	conf.NYPL40000=numpy.zeros(32,dtype=int)
	conf.NZPL40000=numpy.zeros(32,dtype=int)
	conf.NXPL100000=numpy.zeros(32,dtype=int)
	conf.NYPL100000=numpy.zeros(32,dtype=int)
	conf.NZPL100000=numpy.zeros(32,dtype=int)
	conf.NRPL2=numpy.zeros(32,dtype=int)
	conf.NRPL10=numpy.zeros(32,dtype=int)
	conf.NRPL40=numpy.zeros(32,dtype=int)
	conf.NRPL100=numpy.zeros(32,dtype=int)
	conf.NRPL400=numpy.zeros(32,dtype=int)
	conf.NRPL1000=numpy.zeros(32,dtype=int)
	conf.NRPL4000=numpy.zeros(32,dtype=int)
	conf.NRPL10000=numpy.zeros(32,dtype=int)
	conf.NRPL40000=numpy.zeros(32,dtype=int)
	conf.NRPL100000=numpy.zeros(32,dtype=int) #22678
	conf.NEPL1=numpy.zeros(101,dtype=int)
	conf.NEPL10=numpy.zeros(101,dtype=int)
	conf.NEPL100=numpy.zeros(101,dtype=int)
	conf.MELEC=numpy.zeros(1001,dtype=int)
	conf.MELEC3=numpy.zeros(1001,dtype=int)
	conf.MELEC10=numpy.zeros(1001,dtype=int)
	conf.MELEC30=numpy.zeros(1001,dtype=int)
	conf.MELEC100=numpy.zeros(1001,dtype=int)
	conf.MELEC300=numpy.zeros(1001,dtype=int) #22689
	# C ZERO ARRAYS
	conf.XAV=numpy.zeros(100001)
	conf.YAV=numpy.zeros(100001)
	conf.ZAV=numpy.zeros(100001)
	conf.TAV=numpy.zeros(100001)
	conf.XYAV=numpy.zeros(100001)
	conf.XYZAV=numpy.zeros(100001)
	conf.DX=numpy.zeros(100001)
	conf.DY=numpy.zeros(100001)
	conf.DZ=numpy.zeros(100001)
	conf.DT=numpy.zeros(100001)
	conf.DXY=numpy.zeros(100001)
	conf.DXYZ=numpy.zeros(100001)
	conf.FARX1=numpy.zeros(100001)
	conf.FARY1=numpy.zeros(100001)
	conf.FARZ1=numpy.zeros(100001)
	conf.FARXY1=numpy.zeros(100001)
	conf.RMAX1=numpy.zeros(100001)
	conf.TSUM=numpy.zeros(100001)
	conf.XNEG=numpy.zeros(100001)
	conf.YNEG=numpy.zeros(100001)
	conf.ZNEG=numpy.zeros(100001)
	conf.EDELTA=numpy.zeros(100001)
	conf.EDELTA2=numpy.zeros(100001)
	conf.NCL=numpy.zeros(100001)
	conf.NCLEXC=numpy.zeros(100001) ##22716 #22915
	# ----------------------------------------------------
	# if NSEED = 0 : USE STANDARD SEED VALUE =54217137
	if(conf.NSEED != 0):
		RM48(conf.NSEED,0,0)
	#-----------------------------------------------
	#
	CORR=ABZERO*conf.TORR/(ATMOS*(ABZERO+conf.TEMPC)*100.00)                    #check precision
	conf.AKT=(ABZERO+conf.TEMPC)*BOLTZ
	conf.AN1=conf.FRAC[1]*CORR*ALOSCH
	conf.AN2=conf.FRAC[2]*CORR*ALOSCH
	conf.AN3=conf.FRAC[3]*CORR*ALOSCH
	conf.AN4=conf.FRAC[4]*CORR*ALOSCH
	conf.AN5=conf.FRAC[5]*CORR*ALOSCH
	conf.AN6=conf.FRAC[6]*CORR*ALOSCH
	conf.AN=float(100.00*CORR*ALOSCH)
	conf.AN=100.00*CORR*ALOSCH
	#VAN1=FRAC[1]*CORR*CONST4*1.0D15
	#VAN2=FRAC[2]*CORR*CONST4*1.0D15
	#VAN3=FRAC(3)*CORR*CONST4*1.0D15
	#VAN4=FRAC[4]*CORR*CONST4*1.0D15
	#VAN5=FRAC[5]*CORR*CONST4*1.0D15
	#VAN6=FRAC[6]*CORR*CONST4*1.0D15
	#VAN=100.00*CORR*CONST4*1.0D15
	conf.VAN1=conf.FRAC[1]*CORR*ALOSCH*conf.VC
	conf.VAN2=conf.FRAC[2]*CORR*ALOSCH*conf.VC
	conf.VAN3=conf.FRAC[3]*CORR*ALOSCH*conf.VC
	conf.VAN4=conf.FRAC[4]*CORR*ALOSCH*conf.VC
	conf.VAN5=conf.FRAC[5]*CORR*ALOSCH*conf.VC
	conf.VAN6=conf.FRAC[6]*CORR*ALOSCH*conf.VC
	conf.VAN=float(100.00*CORR*ALOSCH*conf.VC)    #22745 #22945
	# CALCULATE AND STORE ENERGY GRID FOR XRAYS BETAS OR PARTICLES
	E=numpy.zeros(20001)
	conf.GAM=numpy.zeros(20001)
	conf.BET=numpy.zeros(20001)
	if(conf.EFINAL <= 20000.0):
		conf.ESTEP=float(conf.EFINAL/float(conf.NSTEP))
		EHALF=float(conf.ESTEP/2.00)
		conf.E[1]=EHALF
		conf.GAM[1]=(conf.EMS+conf.E[1])/conf.EMS
		conf.BET[1]=math.sqrt(1.00-1.00/(conf.GAM[1]*conf.GAM[1]))  #ifcontinues
		for I in range(2,20000+1):                      #ifcontinues
			AJ=float(I-1)
			conf.E[I]=EHALF+conf.ESTEP*AJ
			conf.GAM[I]=(conf.EMS+conf.E[I])/conf.EMS
			conf.BET[I]=math.sqrt(1.00-1.00/(conf.GAM[I]*conf.GAM[I]))
	elif(conf.EFINAL > 20000.0 and conf.EFINAL <= 140000.) :
		conf.ESTEP=1.0
		EHALF=0.5
		conf.E[1]=EHALF
		conf.GAM[1]=(conf.EMS+conf.E[1])/conf.EMS
		conf.BET[1]=math.sqrt(1.00-1.00/(conf.GAM[1]*conf.GAM[1]))
		for i in range(2,16000+1):
			AJ=float(I-1)
			conf.E[I]=EHALF+conf.ESTEP*AJ
			conf.GAM[I]=(conf.EMS+conf.E[I])/conf.EMS
			conf.BET[I]=math.sqrt(1.00-1.00/(conf.GAM[I]*conf.GAM[I]))   #22768 #22968
		ESTEP1=(conf.EFINAL-16000.0)/float(4000)
		for I in range(16001,2000+1):
			AJ=float(I-16000)
			conf.E[I]=16000.0+AJ*ESTEP1
			conf.GAM[I]=(conf.EMS+conf.E[I])/conf.EMS
			conf.BET[I]=math.sqrt(1.00-1.00/(conf.GAM[I]*conf.GAM[I]))
	else:
		conf.ESTEP=1.0
		EHALF=0.5
		conf.E[1]=EHALF
		conf.GAM[1]=(conf.EMS+conf.E[1])/conf.EMS
		conf.BET[1]=math.sqrt(1.00-1.00/(conf.GAM[1]*conf.GAM[1]))
		for I in range(2,12000+1):
			AJ=float(I-1)
			conf.E[I]=EHALF+conf.ESTEP*AJ
			conf.GAM[I]=(conf.EMS+conf.E[I])/conf.EMS
			conf.BET[I]=math.sqrt(1.00-1.00/(conf.GAM[I]*conf.GAM[I]))
		ESTEP1=20.0
		for I in range(12001,16000+1):
			AJ=float(I-12000)
			conf.E[I]=12000.0+AJ*ESTEP1
			conf.GAM[I]=(conf.EMS+conf.E[I])/conf.EMS
			conf.BET[I]=math.sqrt(1.00-1.00/(conf.GAM[I]*conf.GAM[I]))
		ESTEP2=(conf.EFINAL-92000.0)/float(4000)
		for I in range(16001,20000+1):
			AJ=float(I-16000)
			conf.E[I]=92000.0+AJ*ESTEP2
			conf.GAM[I]=(conf.EMS+conf.E[I])/conf.EMS
			conf.BET[I]=math.sqrt(1.00-1.00/(conf.GAM[I]*conf.GAM[I]))
	# endif
	#  RADIANS PER PICOSECOND
	conf.WB=AWB*conf.BMAG*1.0*(10**-12 )
	#   METRES PER PICOSECOND

	if(conf.BMAG == 0.00):
		return LAST
	conf.EOVB=conf.EFIELD*1*(10**-9)/conf.BMAG
	return LAST

	GOTO999()
	# end

# SETUP(0)


"""
PVSource.py

Author: Matthew Yu, Array Lead (2020).
Contact: matthewjkyu@gmail.com
Created: 11/14/20
Last Modified: 11/24/20

Description: The PVSource (Photovoltaic Cell/Module/Subarray) class is a
concrete base class that provides a common API for derived classes to use. The
PVCell class enables users to retrieve information of the PV model, such as
IV curves, maximum power points, and so on given a set of input conditions.

The following paper discusses how to model multiple module PV sources with
variable shading:

    Accurate Modeling of Partially Shaded PV Arrays (Meyers
    et Mikofski)

    A library developed by these authors is called PVMismatch;
    it can be found at [https://github.com/SunPower/PVMismatch].
    We can potentially draw inspiration on this work to build
    PVSource (This is very hard, since I don't understand their
    code!)

    Attribution of the library:

    Mark Mikofski, Bennet Meyers, Chetan Chaudhari (2018).
    “PVMismatch Project: https://github.com/SunPower/PVMismatch".
    SunPower Corporation, Richmond, CA.

TODO: for now, we'll just use the first module we come across when generating
current and associated IV curve characteristics.
"""
# Library Imports.
import numpy as np
import sys

# Custom Imports.
from ArraySimulation.PVSource.PVCell.PVCellIdeal import PVCellIdeal
from ArraySimulation.PVSource.PVCell.PVCellNonideal import PVCellNonideal

class PVSource:
    """
    The PVSource (Photovoltaic source, which encompasses cells, modules, and
    subarrays), is a concrete base class that provides a common API for clients
    to use. The PVSource class enables users to retrieve information on the
    PVSource model, such as IV curves, maximum power points, and so on given a
    set of input conditions.
    """

    # The upper voltage bound that should be tested by any model for a single
    # cell. We expect the PV to always be at open circuit voltage at this point.
    # Adjustable based on the number of cells determined from the initialization.
    MAX_CELL_VOLTAGE = 0.8

    # Our starting upper current bound when looking for the minimum current of
    # a set of modules in series with bypass diodes.
    MAX_CURRENT = 8

    # Out starting lower current bound when looking for the I-V curve of a set
    # of modules in series with bypass diodes.
    MIN_CURRENT = 0

    def __init__(self):
        # Determines the model used by each cell. Every cell gets the same model.
        self._modelType = None

        # Controls whether each cell in a model calculates its current using a
        # lookup table or not.
        self._useLookup = None

    def setupModel(self, modelType="Default", useLookup=True):
        """
        Sets up the initial source parameters.

        Parameters
        ----------
        modelType: String
            Specifies the PVCell model used for modeling all photovoltaics.
        useLookup: Bool
            Enables the use of lookup tables, if they exist for the model. If it
            doesn't, we default to the getCurrent function that doesn't use
            lookups.
        """
        self._modelType = modelType
        if modelType == "Ideal":
            self._model = PVCellIdeal(useLookup)
        elif modelType == "Nonideal":
            self._model = PVCellNonideal(useLookup)
        else:
            self._model = None

        self._useLookup = useLookup

    def getModuleCurrent(self, moduleDef):
        """
        Calculates and returns the source model current for a specific module
        given various environmental parameters.

        Parameters
        ----------
        moduleDef: Dict
            A dictionary for a single module, in the following format:

            moduleDef = {
                "numCells": int,
                "voltage": float,       (V)
                "irradiance": float,    (W/m^2)
                "temperature": float,   (C)
            }

        Returns
        -------
        float|None:
            Current of the module model or None if the model is not defined.
        Throws an exception for undefined cell model.

        Assumptions
        -----------
        Current is roughly linear to the number of cells in series.
        """
        if self._model is not None:
            if self._useLookup:
                return self._model.getCurrentLookup(
                    moduleDef["numCells"],
                    moduleDef["voltage"],
                    moduleDef["irradiance"],
                    moduleDef["temperature"],
                )
            else:
                return self._model.getCurrent(
                    moduleDef["numCells"],
                    moduleDef["voltage"],
                    moduleDef["irradiance"],
                    moduleDef["temperature"],
                )
        else:
            raise Exception("No cell model is defined for the PVSource.")

    def getSourceCurrent(self, modulesDef):
        """
        Calculates and returns the source model current given various
        environmental parameters.

        Parameters
        ----------
        modulesDef: Dict
            A dictionary for a set of modules representing the source, in the
            following format:

            modulesDef = {
                "0": {
                    "numCells": int,
                    "voltage": float,       (V)
                    "irradiance": float,    (W/m^2)
                    "temperature": float,   (C)
                },
                ...
            }

        Returns
        -------
        float|None:
            Current of the source model or None if the model is not defined.

        Assumptions
        -----------
        Current is roughly linear to the number of cells in series.
        """
        if self._model is not None:
            # Go through each module and look for the current.
            moduleCurrents = {}
            for (moduleKey, moduleVals) in modulesDef.items():
                moduleCurrents[moduleKey] = self.getModuleCurrent(moduleVals)

            # Sort the list in descending current order.
            moduleCurrentsSorted = sorted(
                moduleCurrents.items(), key=lambda item: -item[1]
            )

            # Get the list of currents again, but each successive module has
            # numCells incremented by 1.
            currents = []
            curCellNum = 0
            for moduleKey in moduleCurrentsSorted:
                module = modulesDef[moduleKey[0]]
                currents.append(
                    self.getModuleCurrent(
                        {
                            "numCells": module["numCells"] + curCellNum,
                            "voltage": module["voltage"], # TODO: getModuleCurrent can't deal with >1 cell
                            "irradiance": module["irradiance"],
                            "temperature": module["temperature"],
                        }
                    )
                )
                curCellNum += module["numCells"]

            current = max(currents) * (
                1 - np.exp(-1000)  # TODO: this is a magic number for now.
            )
            return current
        else:
            raise Exception("No cell model is defined for the PVSource.")

    def getIV(self, modulesDef, numCells, resolution=0.01):
        """
        TODO: implement multimodule support
        Calculates the entire source model current voltage plot given various
        environmental parameters.

        Parameters
        ----------
        modulesDef: Dict
            A dictionary for a set of modules representing the source, in the
            following format:

            modulesDef = {
                "0": {
                    "numCells": int,
                    "voltage": float,       (V)
                    "irradiance": float,    (W/m^2)
                    "temperature": float,   (C)
                },
                ...
            }
        numCells: int
            Total number of cells in the source.
        resolution: float
            Voltage stride across the source. Occurs within the bounds of [0,
            MAX_VOLTAGE], inclusive.

        Returns
        -------
        list: [(voltage:float, current:float), ...]
            A list of paired voltage|current tuples across the cell IV curve.

        Assumptions
        -----------
        The IV curve of the source has a short circuit current of 0A at
        MAX_VOLTAGE.
        """
        # We need to calculate the expected maximum voltage that can be applied
        # over all modules.
        model = []
        if self._model is not None:
            for voltage in np.arange(
                0, round(PVSource.MAX_CELL_VOLTAGE * numCells, 2) + 0.01, 0.01
            ):
                for module in modulesDef.values():
                    module["voltage"] = voltage
                current = self.getSourceCurrent(modulesDef)
                voltCurrPair = (voltage, current)
                model.append(voltCurrPair)
            return model
        else:
            raise Exception("No cell model is defined for the PVSource.")

    def getEdgeCharacteristics(self, modulesDef, numCells, resolution=0.01):
        """
        Calculates the source model edge characteristics given various
        environmental parameters.

        Parameters
        ----------
        modulesDef: Dict
            A dictionary for a set of modules representing the source, in the
            following format:

            modulesDef = {
                "0": {
                    "numCells": int,
                    "voltage": float,       (V)     <- This is ignored.
                    "irradiance": float,    (W/m^2)
                    "temperature": float,   (C)
                },
                ...
            }
        numCells: int
            Total number of cells in the source.
        resolution: float
            Voltage stride across the source. Occurs within the bounds of [0,
            MAX_VOLTAGE], inclusive.

        Returns
        -------
        tuple: (V_OC:float, I_SC:float, (V_MPP:float, I_MPP:float)):
            A tuple of tuples indicating the open circuit voltage, the short
            circuit current, and the GLOBAL maximum power point (MPP) voltage
            and current.
        """
        if self._model is not None:
            mpp = (0, 0)  # voltage, current list
            OCVoltage = 0.0

            if resolution <= 0:
                resolution = self.MIN_RESOLUTION

            model = self.getIV(modulesDef, numCells, resolution)

            if model != []:
                SCCurrent = model[0][1]  # Current in first entry
                for (voltage, current) in model:
                    if mpp[0] * mpp[1] < voltage * current:
                        mpp = (voltage, current)
                    if OCVoltage != 0.0 and current == 0:
                        OCVoltage = voltage

                return (OCVoltage, SCCurrent, mpp)
            else:
                return (0, 0, (0, 0))
        else:
            raise Exception("No cell model is defined for the PVSource.")

    def getModelType(self):
        """
        Returns the model type used for each PVCell in PVSource.

        Return
        ------
        String: Model type name.
        """
        return self._modelType