# -*- coding: utf-8 -*-
"""
Created on Wed Nov 11 10:41:07 2020

@author: Grayson Schaer
"""

# Deep RL networks + autoencoder
from MLP_Actor import Model as actor_nn
from MLP_Critic import Model as critic_nn
from Autoencoder import Autoencoder

# Number manipulation
import torch
import numpy as np
from scipy import interpolate
from scipy import integrate

# File saving and formatting
import pickle
import os

# Rendering and plotting
import matplotlib.pyplot as plt

class Agent:

    # Initializes a PPO agent
    # @param The number of additional inputs beyond the state image given to the agent during action generation
    # @param The number of outputs generated by the actor (The number of inputs to the environment)
    # @param The number of (state, action, reward) tuples per trajectory
    # @param The number of trajectories per batch
    # @param The number of optimization epochs taken per batch
    # @param The discount ratio
    # @param GAE lambda
    # @param PPO clipping ratio
    # @param Initial learning rate
    # @param Learning rate exponential decay rate
    # @param Path from which to load previously trained autoencoder or agent
    # @param Boolean flag indicating whether to reset actor stdev
    def __init__(self, num_additional_inputs, num_outputs, steps_per_trajectory, trajectories_per_batch,
                 epochs_per_batch, gamma, lamb, epsilon, alpha, decay_rate, load_path, reset_std):

        # Find load file for either offline trained autoencoder or previously trained PPO agent
        if not os.path.exists(load_path + "/output"):
            raise RuntimeError("Could not find load file: " + load_path)
        with open(load_path + "/output", 'rb') as file:
            loaded_data = pickle.load(file)

        # Load offline trained autoencoder
        if ( 'model' in loaded_data ):

            # Build encoder for input image compression
            self.encoder = Autoencoder(0.0, 0.0, load_path=load_path)

            # Gather and store shape data
            self.x_dim = self.encoder.dim_1
            self.y_dim = self.encoder.dim_2
            self.len_encoded_image = self.encoder.model.bottleneck
            self.num_additional_states = num_additional_inputs + self.encoder.num_latent + num_outputs
            self.num_outputs = num_outputs

            # Build actor and critic models
            self.actor = actor_nn(self.len_encoded_image, self.num_additional_states, self.num_outputs)
            self.critic = critic_nn(self.len_encoded_image, self.num_additional_states)

            # Set previous training memory to {}
            self.prev_r_per_episode = np.array([])
            self.prev_value_error = np.array([])
            self.prev_actor_lr = np.array([])
            self.prev_critic_lr = np.array([])
            self.prev_x_stdev = np.array([])
            self.prev_y_stdev = np.array([])
            self.prev_mag_stdev = np.array([])

        # Load previously trained PPO agent
        elif ( ('actor' in loaded_data) and ('critic' in loaded_data) and ('encoder' in loaded_data) ):

            # Load encoder, actor, and critic models
            self.encoder = loaded_data['encoder']
            self.actor = loaded_data['actor']
            self.critic = loaded_data['critic']

            # Gather and store shape data
            self.x_dim = self.encoder.dim_1
            self.y_dim = self.encoder.dim_2
            self.len_encoded_image = self.encoder.model.bottleneck
            self.num_additional_states = self.actor.num_additional_states
            self.num_outputs = self.actor.num_outputs

            # Load previous training memory
            self.prev_r_per_episode = loaded_data['r_per_episode']
            self.prev_value_error = loaded_data['value_error']
            self.prev_actor_lr = loaded_data['actor_lr']
            self.prev_critic_lr = loaded_data['critic_lr']
            self.prev_x_stdev = loaded_data['x_stdev']
            self.prev_y_stdev = loaded_data['y_stdev']
            self.prev_mag_stdev = loaded_data['mag_stdev']

        # Throw error if autoencoder or PPO agent not found
        else:
            raise RuntimeError("Invalid data type at: " + load_path)

        # Batch memory
        self.state_vectors = [[]]
        self.actions = [[]]
        self.rewards = [[]]
        self.old_log_probs = [[]]
        self.advantage_estimates = [[]]
        self.value_targets = [[]]

        # Training parameters
        self.steps_per_trajectory = steps_per_trajectory
        self.trajectories_per_batch = trajectories_per_batch
        self.steps_per_batch = steps_per_trajectory * trajectories_per_batch
        self.epochs_per_batch = epochs_per_batch

        # Hyperparameters
        self.gamma = gamma
        self.lamb = lamb
        self.epsilon = epsilon
        self.alpha = alpha
        self.decay_rate = decay_rate

        # Reset the actor stdev if requested
        if reset_std:
            self.actor.reset_stdev()

        # Create optimizer and lr scheduler for actor
        self.actor_optimizer = torch.optim.Adam(self.actor.parameters() , lr=self.alpha)
        self.actor_lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer=self.actor_optimizer, gamma=self.decay_rate)

        # Create optimizer and lr scheduler for critic
        self.critic_optimizer =  torch.optim.Adam(self.critic.parameters() , lr=self.alpha)
        self.critic_lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer=self.critic_optimizer, gamma=self.decay_rate)

        # Get device
        self.device = self.get_device()
        self.actor.to(self.device)
        self.critic.to(self.device)
        print("Agent Device(")
        print("  " + self.device)
        print(")\n")
        print("Actor " + str(self.actor))
        print("")
        print("Critic " + str(self.critic))

    # Gets the cpu or gpu on which to run NN
    # @return device code
    def get_device(self):

        if torch.cuda.is_available():
            device = 'cuda:0'
        else:
            device = 'cpu'
        return device

    # Ensures all inputs to PPO agent are correct dimensions and type
    # @param The image of the state to be encoded
    # @param Vector containing any addition inputs generated by online estimators
    # @param Vector containing the current state of the output in environment units
    # @param Vector containing the actions generated by the PPO agent in the previous simulation step in agent units
    # @param Scalar reward from previous simulation step
    def run_check(self, state_image, additional_inputs=None, output_state=None, action=None, reward=None):

        # Check state image dimensions
        state_image = np.array(state_image)
        largest_dim = max(state_image.shape)
        smallest_dim = min(state_image.shape)
        if (largest_dim != self.x_dim and largest_dim != self.y_dim) or (smallest_dim != self.x_dim and smallest_dim != self.y_dim):
            raise ValueError("State image dimensions are invalid. Expected (" + str(self.x_dim) + ", " + str(self.y_dim) + ") but got " + str(state_image.shape))

        # Check state image type
        if (state_image.dtype.kind != 'f') and (state_image.dtype.kind != 'i') and (state_image.dtype.kind != 'u'):
            raise TypeError("State image must be type 'f', 'u', or 'i'")

        if additional_inputs != None:
            # Check additional inputs length
            additional_inputs = np.array(additional_inputs)
            if(len(additional_inputs) != self.num_additional_states - self.encoder.num_latent - self.num_outputs):
                raise ValueError("Additional inputs length is invalid. Expected " + str(self.num_additional_states - self.encoder.num_latent - self.num_outputs) + " but got " + str(len(additional_inputs)))

            # Check additional inputs type
            if (additional_inputs.dtype.kind != 'f') and (additional_inputs.dtype.kind != 'i') and (additional_inputs.dtype.kind != 'u'):
                raise TypeError("Additional inputs must be type 'f', 'u', or 'i'")

        if output_state != None:
            # Check output states length
            output_state = np.array(output_state)
            if(len(output_state) != self.num_outputs):
                raise ValueError("Output states length is invalid. Expected " + str(self.num_outputs) + " but got " + str(len(output_state)))

            # Check output states type
            if (output_state.dtype.kind != 'f') and (output_state.dtype.kind != 'i') and (output_state.dtype.kind != 'u'):
                raise TypeError("Output states must be type 'f', 'u', or 'i'")

        if action != None:
            # Check actions length
            action = np.array(action)
            if(len(action) != self.num_outputs):
                raise ValueError("Action length is invalid. Expected " + str(self.num_outputs) + " but got " + str(len(action)))

            # Check actions type
            if (action.dtype.kind != 'f'):
                raise TypeError("Action must be type 'f'")

        if reward != None:
            # Check reward type
            if not isinstance(reward, float):
                raise TypeError("Reward must be type 'f'")

    # Forward propagates an input image through the autoencoder
    # @param The input image to be propogated
    # @return The propogated first output image
    def forward(self, input_image):

        if self.encoder.num_latent >= 1:
            output_images, _, _ = self.encoder.forward(input_image)
        else:
            output_images, _ = self.encoder.forward(input_image)

        output_image = output_images[0,:,:]
        return output_image.tolist()

    # Converts state image, additional inputs, and output state into single state vector
    # @ param The state image in which the policy is applied to calculate the action
    # @ param Any additional inputs generated by external estimators used to inform action generation
    # @ param The current state of the output in environment units
    # @ return Concatenated vector of encoded state image, latent vars, additional inputs, and output state
    def get_state_vector(self, state_image, additional_inputs, output_state, action=None, reward=None):
        # Check inputs
        self.run_check(state_image, additional_inputs=additional_inputs, output_state=output_state, action=action, reward=reward)

        # Get the gaussian distribution parameters used to sample the action for the old and new policy
        with torch.no_grad():

            # Encode the state image and extract the encoded variables using the encoder
            if self.encoder.num_latent >= 1:
                encoded_image, latent_vars = self.encoder.encode(state_image)
            else:
                encoded_image = self.encoder.encode(state_image)

            # Combine the encoded image, latent variables, additional states, and output state into a single state vector
            if self.encoder.num_latent >= 1:
                states = list(np.concatenate((encoded_image, latent_vars)))
            else:
                states = list(encoded_image)
            for i in range(len(additional_inputs)):
                states.append(additional_inputs[i])
            for i in range(len(output_state)):
                states.append(output_state[i])

        return np.array(states)

    # Converts and formats state vector list like object for actor or critic use
    # @param Concatenated vector of encoded state image, latent vars, additional inputs, and output state
    # @return Reshaped tensor representation of state vector for actor or critic use
    def format_state_vector(self, state_vector):

        state_vector = torch.tensor(state_vector)
        state_vector = state_vector.reshape(1,state_vector.shape[0]).float().to(self.device)
        return state_vector

    # Gets the encoded latent variables generated by the encoder based on the state image
    # @param The state image in which the policy is applied to calculate the action
    # @return Tuple containing all latent variables, if any
    def get_latent_vars(self, state_image):
        # Check inputs
        self.run_check(state_image)

        # Get the gaussian distribution parameters used to sample the action for the old and new policy
        with torch.no_grad():

            # Encode the state image and extract the encoded variables using the encoder
            if self.encoder.num_latent >= 1:
                _, latent_vars = self.encoder.encode(state_image)
                latent_vars = tuple(list(latent_vars))
                return latent_vars

            else:
                return None

    # Calcuates determinisitc action given state and policy.
    # @ param The state image in which the policy is applied to calculate the action
    # @ param Any additional inputs generated by external estimators used to inform action generation
    # @ param The current state of the output in environment units
    # @ return The calculated deterministic action based on the state and policy in agent units
    def get_greedy_action(self, state_image, additional_inputs, output_state):

        # Get state vector
        state_vector = self.get_state_vector(state_image, additional_inputs, output_state)

        # Get the gaussian distribution parameters used to sample the action for the old and new policy
        with torch.no_grad():

            # Format state vector
            state_vector = self.format_state_vector(state_vector)

            # Forward propogate formatted state
            means, stdevs = self.actor.forward(state_vector)
            means = means.squeeze().to('cpu')

        # Return the actions
        actions = []
        for i in range(self.num_outputs):
            actions.append(means[i].item())
        return tuple(actions)

    # Calcuates stochastic action given state and policy.
    # @ param The state image in which the policy is applied to calculate the action
    # @ param Any additional inputs generated by external estimators used to inform action generation
    # @ param The current state of the output in environment units
    # @ return The calculated stochastic action based on the state and policy in agent units
    # @ return The calculated stdev based on the policy in agent units
    def get_action(self, state_image, additional_inputs, output_state):

        # Get state vector
        state_vector = self.get_state_vector(state_image, additional_inputs, output_state)

        # Get the gaussian distribution parameters used to sample the action for the old and new policy
        with torch.no_grad():

            # Format state vector
            state_vector = self.format_state_vector(state_vector)

            # Forward propogate formatted state
            means, stdevs = self.actor.forward(state_vector)
            means = means.squeeze().to('cpu')

            # Sample the actions
            action = []
            stdev = []
            for i in range(self.num_outputs):
                dist = torch.distributions.normal.Normal(means[i].item(), stdevs[i].to('cpu'))
                action.append(dist.sample().item())
                stdev.append(stdevs[i].item())

        # Return the actions and stdevs
        return tuple(action) + tuple(stdev)

    # Updates the trajectory memory given an arbitrary time step
    # @param The image of the state to be encoded
    # @param Vector containing any addition inputs generated by online estimators
    # @param Vector containing the current state of the output in environment units
    # @param Vector containing the actions generated by the PPO agent in the previous simulation step in agent units
    # @param Scalar reward from previous simulation step
    # @return List of actor learning rate, critic learning rate, and critic loss during optimization
    def update_agent(self, state_image, additional_inputs, output_state, action, reward):

        # Get state vector
        state_vector = self.get_state_vector(state_image, additional_inputs, output_state, action=action, reward=reward)

        # Update the action and reward memory
        self.state_vectors[-1].append(state_vector)
        self.actions[-1].append(np.array(action))
        self.rewards[-1].append(reward)

        # Get the current (will become the old during learning) log probs
        with torch.no_grad():

            # Format state vector
            state_vector = self.format_state_vector(state_vector)

            # Forward propogate formatted state
            means, stdevs = self.actor.forward(state_vector)
            means = means.squeeze().to('cpu')
            stdevs = stdevs.to('cpu')

            # Get log prob of actions selected
            action = torch.tensor(action)
            dist = torch.distributions.normal.Normal(means, stdevs)

            # Update the old log prob memory
            self.old_log_probs[-1].append(dist.log_prob(action).sum())

            # Gather current value estimates. Will be used for advantage estimate and value target calculations
            self.value_targets[-1].append(self.critic.forward(state_vector).item())

        # If the current trajectory is complete, calculate advantage estimates, value targets, and add another trajectory column to the batch memory
        if len(self.state_vectors[-1]) == self.steps_per_trajectory:

            # Bootstrap value estimates with 0.0
            self.value_targets[-1].append(0.0)

            # Compute deltas for GAE
            self.advantage_estimates[-1] = np.array(self.rewards[-1]) + (self.gamma * np.array(self.value_targets[-1][1:])) - np.array(self.value_targets[-1][:-1])

            # Calculate advantage estimates using GAE
            for t in reversed(range(len(self.state_vectors[-1]) - 1)):
                self.advantage_estimates[-1][t] = self.advantage_estimates[-1][t] + (self.gamma * self.lamb * self.advantage_estimates[-1][t + 1])

            # Get the value targets using TD(0)
            for t in reversed(range(len(self.state_vectors[-1]))):
                self.value_targets[-1][t] = self.rewards[-1][t] + (self.gamma * self.value_targets[-1][t + 1])
            self.value_targets[-1] = self.value_targets[-1][:-1]

            # Add another trajectory column to the batch memory so long as the batch is not full
            if len(self.state_vectors) != self.trajectories_per_batch:

                self.state_vectors.append([])
                self.actions.append([])
                self.rewards.append([])
                self.old_log_probs.append([])
                self.value_targets.append([])
                self.advantage_estimates.append([])

                return []

            # If a batch is complete, learn
            else:
                # Convert batch data
                with torch.no_grad():
                    self.state_vectors = torch.reshape(torch.tensor(self.state_vectors, dtype=torch.float), (self.steps_per_batch, self.len_encoded_image+self.num_additional_states)).to(self.device)
                    self.actions = torch.reshape(torch.tensor(self.actions, dtype=torch.double), (self.steps_per_batch, self.num_outputs)).to(self.device)
                    self.rewards = torch.reshape(torch.tensor(self.rewards, dtype=torch.double), (-1,))
                    self.old_log_probs = torch.reshape(torch.tensor(self.old_log_probs, dtype=torch.double), (-1,)).to(self.device)
                    self.value_targets = torch.reshape(torch.tensor(self.value_targets, dtype=torch.double), (-1,)).to(self.device)
                    self.advantage_estimates = torch.reshape(torch.tensor(self.advantage_estimates, dtype=torch.double), (-1,)).to(self.device)
                    self.advantage_estimates = (self.advantage_estimates - torch.mean(self.advantage_estimates)) / torch.std(self.advantage_estimates)

                # Store learning rates
                output = []
                output.append(self.actor_lr_scheduler.get_last_lr()[0])
                output.append(self.critic_lr_scheduler.get_last_lr()[0])

                # Actor optimization
                for i in range(self.epochs_per_batch):
                    self.actor_optimizer.zero_grad()
                    means, stdevs = self.actor.forward(self.state_vectors)
                    means = means.to(self.device)
                    stdevs = stdevs.to(self.device)
                    log_prob = torch.tensor(0.0).to(self.device)
                    for j in range(self.num_outputs):
                        dist = torch.distributions.normal.Normal(means[:,j], stdevs[j])
                        log_prob = log_prob + dist.log_prob(self.actions[:,j])
                    prob_ratio = torch.exp(log_prob - self.old_log_probs)
                    loss = -(torch.min(self.advantage_estimates*prob_ratio, self.advantage_estimates*torch.clamp(prob_ratio, 1-self.epsilon, 1+self.epsilon))).mean()
                    loss.backward()
                    self.actor_optimizer.step()
                self.actor_lr_scheduler.step()

                # Critic optimization
                for i in range(self.epochs_per_batch):
                    self.critic_optimizer.zero_grad()
                    value_estimates = self.critic.forward(self.state_vectors).double()
                    loss = torch.nn.MSELoss()(value_estimates[:, 0], self.value_targets)
                    with torch.no_grad():
                        output.append(loss.item())
                    loss.backward()
                    self.critic_optimizer.step()
                self.critic_lr_scheduler.step()

                # After learning, reset the memory
                self.state_vectors = [[]]
                self.inputs = [[]]
                self.actions = [[]]
                self.rewards = [[]]
                self.old_log_probs = [[]]
                self.value_targets = [[]]
                self.advantage_estimates = [[]]

                return output

        return []

class Save_Plot_Render:

    def __init__(self):

        # Set all save params and values to trival
        self.r_per_episode = []
        self.value_error = []
        self.actor_lr = []
        self.critic_lr = []
        self.x_stdev = []
        self.y_stdev = []
        self.mag_stdev = []
        self.input_location_x = []
        self.input_location_y = []
        self.input_percent = []
        self.trigger_power = []
        self.source_power = []
        self.temperature_field = []
        self.cure_field = []
        self.fine_temperature_field = []
        self.fine_cure_field = []
        self.fine_mesh_loc = []
        self.global_fine_mesh_x = []
        self.global_fine_mesh_y = []
        self.max_temperature_field = []
        self.interpolated_temperature_field = []
        self.interpolated_cure_field = []
        self.max_theta_field = []
        self.front_curve = []
        self.front_fit = []
        self.front_velocity = []
        self.front_temperature = []
        self.front_shape_param = []
        self.target = []
        self.time = []
        self.reward = []
        self.mesh_x_z0 = []
        self.mesh_y_z0 = []
        self.mesh_y_x0 = []
        self.mesh_z_x0 = []
        self.max_input_mag = []
        self.exp_const = []
        self.control_speed = []
        self.configs_string = []
        self.specific_heat = []
        self.density = []
        self.volume = []
        self.surface_area = []
        self.heat_transfer_coeff = []
        self.ambient_temp = []
        self.adiabatic_rxn_temp = []
        self.initial_temperature = []

    def store_training_curves(self, r_per_episode, value_error):
        self.r_per_episode = np.array(r_per_episode)
        self.value_error = np.array(value_error)

    def store_lr_curves(self, actor_lr, critic_lr):
        self.actor_lr = np.array(actor_lr)
        self.critic_lr = np.array(critic_lr)

    def store_stdev_history(self, x_stdev, y_stdev, mag_stdev):
        self.x_stdev = np.array(x_stdev)
        self.y_stdev = np.array(y_stdev)
        self.mag_stdev = np.array(mag_stdev)

    def store_input_history(self, input_location_x, input_location_y, input_percent, trigger_power, source_power):
        self.input_location_x = np.array(input_location_x)
        self.input_location_y = np.array(input_location_y)
        self.input_percent = np.array(input_percent)
        self.trigger_power = np.array(trigger_power)
        self.source_power = np.array(source_power)

    def store_field_history(self, temperature_field, cure_field, fine_temperature_field, fine_cure_field, fine_mesh_loc):
        self.temperature_field = np.array(temperature_field)
        self.cure_field = np.array(cure_field)
        self.fine_temperature_field = np.array(fine_temperature_field)
        self.fine_cure_field = np.array(fine_cure_field)
        self.fine_mesh_loc = np.array(fine_mesh_loc)

    def store_front_history(self, front_curve, front_fit, front_velocity, front_temperature, front_shape_param):
        self.front_curve = np.array(front_curve)
        self.front_fit = np.array(front_fit)
        self.front_velocity = np.array(front_velocity)
        self.front_temperature = np.array(front_temperature)
        self.front_shape_param = np.array(front_shape_param)

    def store_target_and_time(self, target, time, reward):
        self.target = np.array(target)
        self.time = np.array(time)
        self.reward = np.array(reward)

    def store_top_mesh(self, mesh_x_z0, mesh_y_z0):
        self.mesh_x_z0 = np.array(mesh_x_z0)
        self.mesh_y_z0 = np.array(mesh_y_z0)

    def store_input_params(self, max_input_mag, exp_const):
        self.max_input_mag = max_input_mag
        self.exp_const = exp_const

    def store_options(self, control_speed, configs_string):
        self.control_speed = (control_speed == 1)
        self.configs_string = configs_string

    def store_monomer_properties(self, specific_heat, density, adiabatic_rxn_temp):
        self.specific_heat = specific_heat
        self.density = density
        self.adiabatic_rxn_temp = adiabatic_rxn_temp

    def store_domain_properties(self, volume, surface_area):
        self.volume = volume
        self.surface_area = surface_area

    def store_boundary_conditions(self, heat_transfer_coeff, ambient_temp, initial_temperature):
        self.heat_transfer_coeff = heat_transfer_coeff
        self.ambient_temp = ambient_temp
        self.initial_temperature = initial_temperature

    def post_process_temperature_field(self):

        # Calculate variables relating to upsampling coarse temperature image
        fine_mesh_x_length = np.mean(self.fine_mesh_loc[:,1]-self.fine_mesh_loc[:,0])
        fine_mesh_x_step = fine_mesh_x_length / (len(self.fine_temperature_field[0,:])-1)
        coarse_mesh_x_length = self.mesh_x_z0[-1,0]-self.mesh_x_z0[0,0]
        num_fine_x_verts_global = round(coarse_mesh_x_length / fine_mesh_x_step) + 1
        coarse_mesh_y_length = self.mesh_y_z0[0,-1]-self.mesh_y_z0[0,0]

        # Define fine resolution mesh over which temperature data will be interpolated
        global_fine_x_linspace = np.linspace(0.0, coarse_mesh_x_length, num_fine_x_verts_global)
        global_fine_y_linspace = np.linspace(0.0, coarse_mesh_y_length, len(self.fine_temperature_field[0,0,:]))
        global_fine_mesh_y, global_fine_mesh_x = np.meshgrid(global_fine_y_linspace, global_fine_x_linspace)

        # Initialize interpolated temperature fields
        interpolated_temp_field = np.zeros( (len(self.time), len(global_fine_x_linspace), len(global_fine_y_linspace)) )
        interpolated_cure_field = np.zeros( (len(self.time), len(global_fine_x_linspace), len(global_fine_y_linspace)) )
        cum_max_temp_field = np.zeros( (len(self.time), len(global_fine_x_linspace), len(global_fine_y_linspace)) )

        # Determine the maximum temperature at fine resolution at each frame
        for curr_frame in range(len(self.time)):

            # At each frame, interpolate the coarse temperautre field to the fine field resolution
            f = interpolate.interp2d(self.mesh_x_z0[:,0], self.mesh_y_z0[0,:], np.transpose(self.temperature_field[curr_frame]))
            interpolated_temp_field[curr_frame] = np.transpose(f(global_fine_x_linspace, global_fine_y_linspace))
            f = interpolate.interp2d(self.mesh_x_z0[:,0], self.mesh_y_z0[0,:], np.transpose(self.cure_field[curr_frame]))
            interpolated_cure_field[curr_frame] = np.transpose(f(global_fine_x_linspace, global_fine_y_linspace))

            # Paint over the interpolated temperature field with the fine temperature field
            fine_mesh_start_loc = self.fine_mesh_loc[curr_frame][0]
            fine_mesh_end_loc = self.fine_mesh_loc[curr_frame][1]
            fine_mesh_start_index = np.argmin(abs(global_fine_x_linspace-fine_mesh_start_loc))
            fine_mesh_end_index = np.argmin(abs(global_fine_x_linspace-fine_mesh_end_loc))
            interpolated_temp_field[curr_frame][fine_mesh_start_index:(fine_mesh_end_index+1),:] = self.fine_temperature_field[curr_frame]
            interpolated_cure_field[curr_frame][fine_mesh_start_index:(fine_mesh_end_index+1),:] = self.fine_cure_field[curr_frame]

            # Compare the fine temperature mesh to the fine resolution corase temperature mesh
            if curr_frame == 0:
                cum_max_temp_field[curr_frame,:,:] = interpolated_temp_field[curr_frame]
            else:
                cum_max_temp_field[curr_frame,:,:] = np.maximum(interpolated_temp_field[curr_frame], cum_max_temp_field[curr_frame-1])

        # Return the maximum temperature field and mesh used to plot it
        return global_fine_mesh_x, global_fine_mesh_y, cum_max_temp_field, interpolated_temp_field, interpolated_cure_field

    def get_steady_state(self):
        # Determine the average front speed in steady state propogation (region wherein front velocity is +- 0.05mm/s of the non zero mean front velocity
        # and the front temperature is +- 5K of the non zero mean front temperature where the first 7.5 seconds are ignored)
        mask = np.array([all(t) for t in zip(self.front_velocity!=0, self.time>=7.5)])
        lower_steady_state_vels = np.int32((self.front_velocity >= np.mean(self.front_velocity[mask]) - 0.00005))
        upper_steady_state_vels = np.int32((self.front_velocity <= np.mean(self.front_velocity[mask]) + 0.00005))
        lower_steady_state_temps = np.int32((self.front_temperature >= np.mean(self.front_temperature[mask]) - 5.0))
        upper_steady_state_temps = np.int32((self.front_temperature <= np.mean(self.front_temperature[mask]) + 5.0))
        steady_state = (lower_steady_state_vels + upper_steady_state_vels + lower_steady_state_temps + upper_steady_state_temps) == 4

        # Calculate disjoint steady ranges. This calculation further imposes that a steady time range must be no less than 5 seconds
        start_indices = []
        end_indices = []
        i=0
        while i < len(steady_state):
            if (i==0 and steady_state[i]):
                start_indices.append(i)
                while(i < len(steady_state) and steady_state[i]):
                    i = i+1
                end_indices.append(i - 1)
            elif(steady_state[i] and not steady_state[i-1]):
                start_indices.append(i)
                while(i < len(steady_state) and steady_state[i]):
                    i = i+1
                end_indices.append(i - 1)
            else:
                i = i + 1
        start_indices = np.array(start_indices) - 1
        start_indices[start_indices<0] = 0
        end_indices = np.array(end_indices) + 1
        end_indices[end_indices>=len(steady_state)] = len(steady_state)-1
        bool_masks = []
        for i in range(len(start_indices)):
            bool_masks.append(np.zeros(len(steady_state)))
            bool_masks[i][start_indices[i]:end_indices[i]+1]=1
            bool_masks[i] = bool_masks[i]==1
        temp_mask = []
        for i in range(len(bool_masks)):
            if (self.time[bool_masks[i]][-1] - self.time[bool_masks[i]][0]) >= 5.0:
                temp_mask.append(bool_masks[i])
        bool_masks = temp_mask

        # Calculate the average steady state speed and front temperature
        average_steady_speed = []
        average_steady_temp = []
        for i in range(len(bool_masks)):
            average_steady_speed.append(list(self.front_velocity[bool_masks[i]]))
            average_steady_temp.append(list(self.front_temperature[bool_masks[i]]))
        if len(bool_masks)!=0:
            average_steady_speed = np.mean(np.array(sum(average_steady_speed,[])))
            average_steady_temp = np.mean(np.array(sum(average_steady_temp,[])))

        return bool_masks, average_steady_speed, average_steady_temp

    def general_save_ops(self, folder_title):
        print("\n\nSaving results...")

        # Find save paths
        done = False
        curr_folder = 1
        while not done:
            path = "../results/"+folder_title+str(curr_folder)
            video_1_path = "../results/"+folder_title+str(curr_folder)+"/video_1/"
            video_2_path = "../results/"+folder_title+str(curr_folder)+"/video_2/"
            if not os.path.isdir(path):
                os.mkdir(path)
                os.mkdir(video_1_path)
                os.mkdir(video_2_path)
                done = True
            else:
                curr_folder = curr_folder + 1

        # Store save paths
        self.path = path
        self.video_1_path = video_1_path
        self.video_2_path = video_2_path

        # Save configs string to data file
        file = open(self.path+"/settings.dat", "w")
        file.write(self.configs_string)
        file.close()

        # Determine mean front locations
        self.mean_front_x_locations = np.zeros(len(self.front_curve))
        self.mean_front_y_locations = np.zeros(len(self.front_curve))
        for curr_frame in range(len(self.front_curve)):
            curr_mean_x = self.front_curve[curr_frame][0]
            curr_mean_x = curr_mean_x[curr_mean_x > 0.0]
            if len(curr_mean_x) != 0:
                self.mean_front_x_locations[curr_frame] = np.mean(curr_mean_x)
            else:
                self.mean_front_x_locations[curr_frame] = 0.0
            curr_mean_y = self.front_curve[curr_frame][1]
            curr_mean_y = curr_mean_y[curr_mean_y > 0.0]
            if len(curr_mean_y) != 0:
                self.mean_front_y_locations[curr_frame] = np.mean(curr_mean_y)
            else:
                self.mean_front_y_locations[curr_frame] = 0.0

        # Calculate the max temperature field
        self.global_fine_mesh_x, self.global_fine_mesh_y, self.cum_max_temp_field, self.interpolated_temp_field, self.interpolated_cure_field = self.post_process_temperature_field()

        # Determine mean initial conditions
        self.mean_initial_temp = np.mean(self.interpolated_temp_field[0]) * (self.adiabatic_rxn_temp - self.initial_temperature) + self.initial_temperature

        # Calculate steady state stuff
        self.steady_masks, self.steady_speed, self.steady_temp = self.get_steady_state()

    def save(self, agent):

        # General save operations
        self.general_save_ops("PPO_")

        # Concatenate previous training results
        if len(agent.prev_r_per_episode) != 0:
            self.r_per_episode = np.concatenate((agent.prev_r_per_episode, self.r_per_episode))
            self.value_error = np.concatenate((agent.prev_value_error, self.value_error))
            self.actor_lr = np.concatenate((agent.prev_actor_lr, self.actor_lr))
            self.critic_lr = np.concatenate((agent.prev_critic_lr, self.critic_lr))
            self.x_stdev = np.concatenate((agent.prev_x_stdev, self.x_stdev))
            self.y_stdev = np.concatenate((agent.prev_y_stdev, self.y_stdev))
            self.mag_stdev = np.concatenate((agent.prev_mag_stdev, self.mag_stdev))

        # Compile all stored data to dictionary
        data = {
            'r_per_episode' : self.r_per_episode,
            'value_error' : self.value_error,
            'actor_lr' : self.actor_lr,
            'critic_lr' : self.critic_lr,
            'x_stdev': self.x_stdev,
            'y_stdev': self.y_stdev,
            'mag_stdev': self.mag_stdev,
            'input_location_x': self.input_location_x,
            'input_location_y': self.input_location_y,
            'input_percent': self.input_percent,
            'trigger_power': self.trigger_power,
            'source_power': self.source_power,
            'temperature_field': self.temperature_field,
            'cure_field': self.cure_field,
            'fine_temperature_field': self.fine_temperature_field,
            'fine_cure_field': self.fine_cure_field,
            'steady_masks' : self.steady_masks,
            'steady_speed' : self.steady_speed,
            'steady_temp' : self.steady_temp,
            'fine_mesh_loc': self.fine_mesh_loc,
            'global_fine_mesh_x': self.global_fine_mesh_x,
            'global_fine_mesh_y': self.global_fine_mesh_y,
            'cum_max_temp_field': self.cum_max_temp_field,
            'interpolated_temp_field' : self.interpolated_temp_field,
            'interpolated_cure_field' : self.interpolated_cure_field,
            'front_curve': self.front_curve,
            'front_fit' : self.front_fit,
            'mean_front_x_locations': self.mean_front_x_locations,
            'mean_front_y_locations': self.mean_front_y_locations,
            'front_velocity': self.front_velocity,
            'front_temperature': self.front_temperature,
            'front_shape_param': self.front_shape_param,
            'target': self.target,
            'time': self.time,
            'reward': self.reward,
            'mesh_x_z0' : self.mesh_x_z0,
            'mesh_y_z0' : self.mesh_y_z0,
            'max_input_mag' : self.max_input_mag,
            'exp_const' : self.exp_const,
            'control_speed' : self.control_speed,
            'configs_string' : self.configs_string,
            'specific_heat' : self.specific_heat,
            'density' : self.density,
            'adiabatic_rxn_temp' : self.adiabatic_rxn_temp,
            'volume' : self.volume,
            'surface_area' : self.surface_area,
            'heat_transfer_coeff' : self.heat_transfer_coeff,
            'ambient_temp' : self.ambient_temp,
            'initial_temperature' : self.initial_temperature,
            'actor': agent.actor,
            'critic': agent.critic,
            'encoder' : agent.encoder,
        }

        # Save the stored data
        with open(self.path + "/output", 'wb') as file:
            pickle.dump(data, file)

    def save_without_agent(self):
        # General save operations
        self.general_save_ops("SIM_")

        # Compile all stored data to dictionary
        data = {
            'input_location_x': self.input_location_x,
            'input_location_y': self.input_location_y,
            'input_percent': self.input_percent,
            'trigger_power': self.trigger_power,
            'source_power': self.source_power,
            'temperature_field': self.temperature_field,
            'cure_field': self.cure_field,
            'fine_temperature_field': self.fine_temperature_field,
            'fine_cure_field': self.fine_cure_field,
            'steady_masks' : self.steady_masks,
            'steady_speed' : self.steady_speed,
            'steady_temp' : self.steady_temp,
            'fine_mesh_loc': self.fine_mesh_loc,
            'global_fine_mesh_x': self.global_fine_mesh_x,
            'global_fine_mesh_y': self.global_fine_mesh_y,
            'cum_max_temp_field': self.cum_max_temp_field,
            'interpolated_temp_field' : self.interpolated_temp_field,
            'interpolated_cure_field' : self.interpolated_cure_field,
            'front_curve': self.front_curve,
            'front_fit' : self.front_fit,
            'mean_front_x_locations': self.mean_front_x_locations,
            'mean_front_y_locations': self.mean_front_y_locations,
            'front_velocity': self.front_velocity,
            'front_temperature': self.front_temperature,
            'front_shape_param': self.front_shape_param,
            'target': self.target,
            'time': self.time,
            'reward': self.reward,
            'mesh_x_z0' : self.mesh_x_z0,
            'mesh_y_z0' : self.mesh_y_z0,
            'max_input_mag' : self.max_input_mag,
            'exp_const' : self.exp_const,
            'control_speed' : self.control_speed,
            'configs_string' : self.configs_string,
            'specific_heat' : self.specific_heat,
            'density' : self.density,
            'adiabatic_rxn_temp' : self.adiabatic_rxn_temp,
            'volume' : self.volume,
            'surface_area' : self.surface_area,
            'heat_transfer_coeff' : self.heat_transfer_coeff,
            'ambient_temp' : self.ambient_temp,
            'initial_temperature' : self.initial_temperature,
        }

        # Save the stored data
        with open(self.path + "/output", 'wb') as file:
            pickle.dump(data, file)

    def plot(self, with_agent):
        print("Plotting results...")

        # Plot the trajectory given the front speed is being controlled
        ## ====================================================================================================================================================================================================== ##
        if self.control_speed:

            # Plot speed trajectory
            plt.clf()
            plt.gcf().set_size_inches(8.5, 5.5)
            if with_agent==1:
                plt.title("Front Velocity",fontsize='xx-large')
            else:
                if len(self.steady_masks) == 0.0:
                    plt.title("Front Velocity",fontsize='xx-large')
                else:
                    plt.title("Front Velocity (Steady = " + '{:.3f}'.format(1000.0*self.steady_speed) + " mm/s)",fontsize='xx-large')
            plt.xlabel("Simulation Time [s]",fontsize='large')
            plt.ylabel("Front Velocity [mm/s]",fontsize='large')
            plt.plot(self.time, 1000.0*self.front_velocity,c='r',lw=2.5,label='Actual')
            plt.plot(self.time, 1000.0*self.target,c='k',ls='--',lw=2.5,label='Target')
            plt.plot(self.time, 1050.0*self.target,c='k',ls=':',lw=2.0,label='Target ± 5%')
            plt.plot(self.time, 950.0*self.target,c='k',ls=':',lw=2.0)
            plt.ylim(0.0, 1500.0*np.max(self.target))
            plt.xlim(0.0, np.round(self.time[-1]))
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.legend(bbox_to_anchor=(1.04, 1.0), loc='upper left',fontsize='large')
            plt.tight_layout()
            plt.savefig(self.path + "/trajectory.svg", dpi = 500)
            plt.close()

            # Plot front temperature trajectory
            sorted_mean_front_x_locations = 1000.0*np.array(sorted(self.mean_front_x_locations))
            sorted_front_temperature = np.array([x for _, x in sorted(zip(self.mean_front_x_locations, self.front_temperature))])-273.15
            plt.clf()
            plt.gcf().set_size_inches(8.5, 5.5)
            if with_agent==1:
                plt.title("Front Temperature",fontsize='xx-large')
            else:
                if len(self.steady_masks) == 0.0:
                    plt.title("Front Temperature",fontsize='xx-large')
                else:
                    plt.title("Front Temperature (Steady = " + '{:.1f}'.format(self.steady_temp-273.15) + " C)",fontsize='xx-large')
                    plt.legend(loc='upper right',fontsize='large')
            plt.xlabel("Location [mm]",fontsize='large')
            plt.ylabel("Front Temperature [C]",fontsize='large')
            plt.plot(sorted_mean_front_x_locations, sorted_front_temperature, c='r', lw=2.5)
            plt.ylim(0.0, 1.025*max(self.front_temperature-273.15))
            plt.xlim(0.0, 1000.0*self.mesh_x_z0[-1,0])
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.savefig(self.path + "/temp.svg", dpi = 500)
            plt.close()

        # Plot the trajectory given the front speed is being controlled
        ## ====================================================================================================================================================================================================== ##
        else:
            # Plot speed trajectory
            plt.clf()
            plt.gcf().set_size_inches(8.5, 5.5)
            if with_agent==1:
                plt.title("Front Velocity",fontsize='xx-large')
            else:
                if len(self.steady_masks) == 0.0:
                    plt.title("Front Velocity",fontsize='xx-large')
                else:
                    plt.title("Front Velocity (Steady = " + '{:.3f}'.format(1000.0*self.steady_speed) + " mm/s)",fontsize='xx-large')
                    plt.legend(loc='upper right',fontsize='large')
            plt.plot(self.time, 1000.0*self.front_velocity,c='r',lw=2.5)
            plt.xlabel("Simulation Time [s]",fontsize='large')
            plt.ylabel("Front Velocity [mm/s]",fontsize='large')
            plt.ylim(0.0, 1.025*max(1000.0*self.front_velocity))
            plt.xlim(0.0, np.round(self.time[-1]))
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.savefig(self.path + "/speed.svg", dpi = 500)
            plt.close()

            # Plot front temperature trajectory
            sorted_mean_front_x_locations = 1000.0*np.array(sorted(self.mean_front_x_locations))
            sorted_front_temperature = np.array([x for _, x in sorted(zip(self.mean_front_x_locations, self.front_temperature))])-273.15
            plt.clf()
            plt.gcf().set_size_inches(8.5, 5.5)
            if with_agent==1:
                plt.title("Front Temperature",fontsize='xx-large')
            else:
                if len(self.steady_masks) == 0.0:
                    plt.title("Front Temperature",fontsize='xx-large')
                else:
                    plt.title("Front Temperature (Steady = " + '{:.1f}'.format(self.steady_temp-273.15) + " C)",fontsize='xx-large')
                plt.xlabel("Location [mm]",fontsize='large')
                plt.ylabel("Front Temperature [C]",fontsize='large')
            plt.plot(sorted_mean_front_x_locations, sorted_front_temperature,c='r',lw=2.5,label='Actual')
            plt.plot(sorted_mean_front_x_locations, self.target-273.15,c='k',ls='--',lw=2.5,label='Target')
            plt.plot(sorted_mean_front_x_locations, 1.05*(self.target-273.15),c='k',ls=':',lw=2.0,label='Target ± 5%')
            plt.plot(sorted_mean_front_x_locations, 0.95*(self.target-273.15),c='k',ls=':',lw=2.0)
            plt.ylim(0.0, 1.5*(np.max(self.target)-273.15))
            plt.xlim(0.0, 1000.0*self.mesh_x_z0[-1,0])
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.legend(bbox_to_anchor=(1.04, 1.0), loc='upper left',fontsize='large')
            plt.tight_layout()
            plt.savefig(self.path + "/trajectory.svg", dpi = 500)
            plt.close()

        # Plot reward trajectory
        ## ====================================================================================================================================================================================================== ##
        plt.clf()
        plt.gcf().set_size_inches(8.5, 5.5)
        plt.title("Reward During Trajectory",fontsize='xx-large')
        plt.xlabel("Simulation Time [s]",fontsize='large')
        plt.ylabel("Reward [-]",fontsize='large')
        plt.plot(self.time, self.reward[:,0],c='k',lw=2.5, label='Total')
        if np.sum(np.abs(self.reward[:,1])) >= 1e-6:
            plt.plot(self.time, self.reward[:,1],c='r',lw=1.0, label='Input Loc')
        if np.sum(np.abs(self.reward[:,2])) >= 1e-6:
            plt.plot(self.time, self.reward[:,2],c='b',lw=1.0, label='Input Mag')
        if np.sum(np.abs(self.reward[:,3])) >= 1e-6:
            plt.plot(self.time, self.reward[:,3],c='g',lw=1.0, label='Max Temp')
        if np.sum(np.abs(self.reward[:,4])) >= 1e-6:
            plt.plot(self.time, self.reward[:,4],c='m',lw=1.0, label='Shape')
        if np.sum(np.abs(self.reward[:,5])) >= 1e-6:
            plt.plot(self.time, self.reward[:,5],c='c',lw=1.0, label='Target')
        plt.xlim(0.0, np.round(self.time[-1]))
        plt.xticks(fontsize='large')
        plt.yticks(fontsize='large')
        plt.legend(bbox_to_anchor=(1.04, 1.0), loc='upper left',fontsize='large')
        plt.tight_layout()
        plt.savefig(self.path + "/reward.svg", dpi = 500)
        plt.close()

        # Plot energy trajectory
        ## ====================================================================================================================================================================================================== ##
        if self.control_speed:

            #Calculate energy required and ideal energy savings
            C1 = 0.00070591
            C2 = 0.0067238
            C3 = 0.53699
            power = self.trigger_power + self.source_power
            energy = integrate.cumtrapz(power, x=self.time)
            energy = np.insert(energy, 0, 0.0)
            trigger_energy = integrate.cumtrapz(self.trigger_power, x=self.time)
            trigger_energy = np.insert(trigger_energy, 0, 0.0)
            target_temp = ((np.sqrt(C2*C2 - 4.0*C1*(C3-1000.0*self.target[-1])) - C2) / (2.0 * C1)) + 273.15
            required_energy = self.specific_heat*self.density*self.volume*(target_temp - self.mean_initial_temp) + self.heat_transfer_coeff*self.surface_area*(target_temp - self.ambient_temp)*self.time + trigger_energy
            ideal_energy = self.specific_heat*self.density*self.volume*(target_temp - self.mean_initial_temp) + trigger_energy

            # Plot energy trajectory
            plt.clf()
            fig, ax1 = plt.subplots()
            fig.set_size_inches(8.5,5.5)
            ax1.set_xlabel("Simulation Time [s]",fontsize='large')
            ax1.set_ylabel("Power [W]",fontsize='large',color='b')
            ax1.plot(self.time, power,c='b',lw=2.5)
            ax1.set_xlim(0.0, np.round(self.time[-1]))
            ax1.tick_params(axis='x', labelsize=12)
            ax1.tick_params(axis='y', labelsize=12, labelcolor='b')
            ax2 = ax1.twinx()
            ax2.set_ylabel("Cumulative Energy Consumed [J]",fontsize='large',color='r')
            ax2.plot(self.time, energy,c='r',lw=2.5)
            ax2.plot(self.time, required_energy,c='k',lw=2.5,ls='--',label='Bulk Heating')
            ax2.plot(self.time, ideal_energy,c='k',lw=2.5,ls=':',label='Local Heating')
            ax2.set_xlim(0.0, np.round(self.time[-1]))
            ax2.tick_params(axis='x', labelsize=12)
            ax2.tick_params(axis='y', labelsize=12, labelcolor='r')
            title_str = "External Energy Input"
            fig.suptitle(title_str,fontsize='xx-large')
            plt.legend(bbox_to_anchor=(1.09, 1.0), loc='upper left',fontsize='large')
            plt.tight_layout()
            plt.savefig(self.path + "/energy.svg", dpi = 500)
            plt.close()

            # Plot cumulative max normalized temperature
            ## ====================================================================================================================================================================================================== ##
            # Calculate colorbar range
            min_temp = np.mean(self.cum_max_temp_field[-1]) - np.mean(self.cum_max_temp_field[-1]) % 0.05
            max_temp = round(np.max(self.cum_max_temp_field[-1]) + 0.05 - (np.max(self.cum_max_temp_field[-1]) % 0.05),2)
            max_temp = min(min_temp + 2*(np.std(self.cum_max_temp_field[-1]) - np.std(self.cum_max_temp_field[-1]) % 0.05), max_temp)

            # Plot cumulative maximum temperature
            plt.clf()
            plt.gcf().set_size_inches(8.5, 2.5)
            c0 = plt.pcolormesh(1000.0*self.global_fine_mesh_x, 1000.0*self.global_fine_mesh_y, self.cum_max_temp_field[-1], shading='gouraud', cmap='jet', vmin=min_temp, vmax=max_temp)
            cbar0 = plt.colorbar(c0)
            cbar0.set_label("ϴ [-]",labelpad=20,fontsize='large')
            cbar0.ax.tick_params(labelsize=12)
            plt.xlabel('X Position [mm]',fontsize='large')
            plt.ylabel('Y Position [mm]',fontsize='large')
            plt.xticks([],fontsize='large')
            plt.yticks([],fontsize='large')
            ax = plt.gca()
            ax.set_axis_off()
            plt.title("Maximum Cumulative Temperature",fontsize='xx-large')
            plt.axis('equal')
            plt.tight_layout()
            plt.savefig(self.path + "/max_cum_temp.png", dpi = 500)
            plt.close()

        # Plot cure profiles
        ## ====================================================================================================================================================================================================== ##
        # Set up profile plot
        plt.clf()
        plt.gcf().set_size_inches(8.5, 5.5)
        plt.xlabel("X Position [mm]",fontsize='large')
        plt.ylabel("α [-]",fontsize='large')
        plt.ylim(-0.1, 1.1)
        plt.xticks(fontsize='large')
        plt.yticks(fontsize='large')

        # Get the temperature and cure profiles at selected times after ignition
        steps = []
        if self.time[-1] > 4.0:
            steps.append(np.argmin(np.abs(self.time - 4.0)))
        if self.time[-1] > 12.0:
            steps.append(np.argmin(np.abs(self.time - 12.0)))
        if self.time[-1] > 20.0:
            steps.append(np.argmin(np.abs(self.time - 20.0)))
        if self.time[-1] > 28.0:
            steps.append(np.argmin(np.abs(self.time - 28.0)))

        # Plot profiles
        beg_plot = 1e6
        end_plot = -1e6
        colors = ['k', 'r', 'b', 'g']
        labels = ['t = 4 s', 't = 12 s', 't = 20 s', 't = 28 s']
        for ind in range(len(steps)):
            curr_step = steps[ind]
            coarse_cure_profile_cen = self.cure_field[curr_step][:,len(self.cure_field[curr_step][0])//2]
            coarse_cure_profile_edge = self.cure_field[curr_step][:,1]
            coarse_end_ind = np.argmin(np.abs(self.mesh_x_z0[:,0] - self.fine_mesh_loc[curr_step][0]))+1
            coarse_start_ind = np.argmin(np.abs(self.mesh_x_z0[:,0] - self.fine_mesh_loc[curr_step][1]))
            coarse_x1_coords = self.mesh_x_z0[0:coarse_end_ind,0]
            coarse_cure1_profile_cen = coarse_cure_profile_cen[0:coarse_end_ind]
            coarse_cure1_profile_edge = coarse_cure_profile_edge[0:coarse_end_ind]
            plt.plot(1000.0*coarse_x1_coords, coarse_cure1_profile_cen, c=colors[ind], lw=2.5)
            plt.plot(1000.0*coarse_x1_coords, coarse_cure1_profile_edge, c=colors[ind], lw=2.0, ls=":")
            coarse_x2_coords = self.mesh_x_z0[coarse_start_ind:-1,0]
            coarse_cure2_profile_cen = coarse_cure_profile_cen[coarse_start_ind:-1]
            coarse_cure2_profile_edge = coarse_cure_profile_edge[coarse_start_ind:-1]
            plt.plot(1000.0*coarse_x2_coords, coarse_cure2_profile_cen, c=colors[ind], lw=2.5)
            plt.plot(1000.0*coarse_x2_coords, coarse_cure2_profile_edge, c=colors[ind], lw=2.0, ls=":")
            fine_cure_profile_cen = self.fine_cure_field[curr_step][:,len(self.fine_cure_field[curr_step][0])//2]
            fine_cure_profile_edge = self.fine_cure_field[curr_step][:,1]
            fine_x_coords = np.linspace(self.fine_mesh_loc[curr_step][0], self.fine_mesh_loc[curr_step][1], len(fine_cure_profile_cen))
            plt.plot(1000.0*fine_x_coords, fine_cure_profile_cen, c=colors[ind], lw=2.5, label=labels[ind]+" center")
            plt.plot(1000.0*fine_x_coords, fine_cure_profile_edge, c=colors[ind], lw=2.0, ls=":", label=labels[ind]+" edge")
            beg_plot = min(beg_plot, max((self.mean_front_x_locations[curr_step] - 0.40*(self.fine_mesh_loc[curr_step][1]-self.fine_mesh_loc[curr_step][0])), 0.0))
            end_plot = max(end_plot, min((self.mean_front_x_locations[curr_step] + 0.40*(self.fine_mesh_loc[curr_step][1]-self.fine_mesh_loc[curr_step][0])), self.mesh_x_z0[-1,0]))

        # Save and close figure
        plt.xlim(1000.0*beg_plot, 1000.0*end_plot)
        plt.title("Cure Profiles",fontsize='xx-large')
        plt.legend(bbox_to_anchor=(1.04, 1.0), loc='upper left',fontsize='large')
        plt.tight_layout()
        plt.savefig(self.path+'/cure_profile.svg', dpi=500)
        plt.close()

        # Plot temperature profiles
        ## ====================================================================================================================================================================================================== ##
        # Set up profile plot
        plt.clf()
        plt.gcf().set_size_inches(8.5, 5.5)
        plt.xlabel("X Position [mm]",fontsize='large')
        plt.ylabel("ϴ [-]",fontsize='large')
        plt.ylim(-0.1, 1.1)
        plt.xticks(fontsize='large')
        plt.yticks(fontsize='large')

        # Get the temperature and cure profiles at selected times after ignition
        steps = []
        if self.time[-1] > 4.0:
            steps.append(np.argmin(np.abs(self.time - 4.0)))
        if self.time[-1] > 12.0:
            steps.append(np.argmin(np.abs(self.time - 12.0)))
        if self.time[-1] > 20.0:
            steps.append(np.argmin(np.abs(self.time - 20.0)))
        if self.time[-1] > 28.0:
            steps.append(np.argmin(np.abs(self.time - 28.0)))

        # Plot profiles
        beg_plot = 1e6
        end_plot = -1e6
        colors = ['k', 'r', 'b', 'g']
        labels = ['t = 4 s', 't = 12 s', 't = 20 s', 't = 28 s']
        for ind in range(len(steps)):
            curr_step = steps[ind]
            coarse_temp_profile_cen = self.temperature_field[curr_step][:,len(self.temperature_field[curr_step][0])//2]
            coarse_temp_profile_edge = self.temperature_field[curr_step][:,1]
            coarse_end_ind = np.argmin(np.abs(self.mesh_x_z0[:,0] - self.fine_mesh_loc[curr_step][0]))+1
            coarse_start_ind = np.argmin(np.abs(self.mesh_x_z0[:,0] - self.fine_mesh_loc[curr_step][1]))
            coarse_x1_coords = self.mesh_x_z0[0:coarse_end_ind,0]
            coarse_temp1_profile_cen = coarse_temp_profile_cen[0:coarse_end_ind]
            coarse_temp1_profile_edge = coarse_temp_profile_edge[0:coarse_end_ind]
            plt.plot(1000.0*coarse_x1_coords, coarse_temp1_profile_cen, c=colors[ind], lw=2.5)
            plt.plot(1000.0*coarse_x1_coords, coarse_temp1_profile_edge, c=colors[ind], lw=2.0, ls=":")
            coarse_x2_coords = self.mesh_x_z0[coarse_start_ind:-1,0]
            coarse_temp2_profile_cen = coarse_temp_profile_cen[coarse_start_ind:-1]
            coarse_temp2_profile_edge = coarse_temp_profile_edge[coarse_start_ind:-1]
            plt.plot(1000.0*coarse_x2_coords, coarse_temp2_profile_cen, c=colors[ind], lw=2.5)
            plt.plot(1000.0*coarse_x2_coords, coarse_temp2_profile_edge, c=colors[ind], lw=2.0, ls=":")
            fine_temp_profile_cen = self.fine_temperature_field[curr_step][:,len(self.fine_temperature_field[curr_step][0])//2]
            fine_temp_profile_edge = self.fine_temperature_field[curr_step][:,1]
            fine_x_coords = np.linspace(self.fine_mesh_loc[curr_step][0], self.fine_mesh_loc[curr_step][1], len(fine_temp_profile_cen))
            plt.plot(1000.0*fine_x_coords, fine_temp_profile_cen, c=colors[ind], lw=2.5, label=labels[ind]+" center")
            plt.plot(1000.0*fine_x_coords, fine_temp_profile_edge, c=colors[ind], lw=2.0, ls=":", label=labels[ind]+" edge")
            beg_plot = min(beg_plot, max((self.mean_front_x_locations[curr_step] - 0.40*(self.fine_mesh_loc[curr_step][1]-self.fine_mesh_loc[curr_step][0])), 0.0))
            end_plot = max(end_plot, min((self.mean_front_x_locations[curr_step] + 0.40*(self.fine_mesh_loc[curr_step][1]-self.fine_mesh_loc[curr_step][0])), self.mesh_x_z0[-1,0]))

        # Save and close figure
        plt.xlim(1000.0*beg_plot, 1000.0*end_plot)
        plt.title("Temperature Profiles",fontsize='xx-large')
        plt.legend(bbox_to_anchor=(1.04, 1.0), loc='upper left',fontsize='large')
        plt.tight_layout()
        plt.savefig(self.path+'/temp_profile.svg', dpi=500)
        plt.close()

        # Plot shape parameter
        ## ====================================================================================================================================================================================================== ##
        # Determine front locations based on front curve data
        front_width = []
        front_skewness = []
        for i in range(len(self.time)):
            front_x_location = self.front_curve[i][0]
            front_x_location = front_x_location[front_x_location >= 0.0]
            front_x_location = 1000.0*front_x_location
            front_instances = len(front_x_location)
            if front_instances > 3:
                front_width.append(np.max(front_x_location) - np.min(front_x_location))
                if not np.std(front_x_location) == 0.0:
                    front_skewness.append(abs((np.mean(front_x_location)-np.median(front_x_location))/np.std(front_x_location)))
                else:
                    front_skewness.append(0.0)
            else:
                front_width.append(0.0)
                front_skewness.append(0.0)
        front_width = np.array(front_width)
        front_skewness = np.array(front_skewness)

        # Set up front plot
        plt.cla()
        plt.clf()
        fig, ax1 = plt.subplots()
        fig.set_size_inches(8.5,5.5)

        # Plot skewness
        ax1.set_xlabel("Time [s]",fontsize='large')
        ax1.set_ylabel("Nonparametric Skew [-]",fontsize='large',color='r')
        ax1.plot(self.time[self.front_shape_param!=1.0],front_skewness[self.front_shape_param!=1.0],lw=2.5,c='r')
        ax1.tick_params(axis='x', labelsize=12)
        ax1.tick_params(axis='y', labelsize=12, labelcolor='r')

        # Plot front width
        ax2 = ax1.twinx()
        ax2.set_ylabel("$\dfrac{W_f}{W}$" + " [-]",fontsize='large',color='b')
        ax2.plot(self.time[self.front_shape_param!=1.0],front_width[self.front_shape_param!=1.0] / (1000.0*self.mesh_y_z0[-1][-1]),lw=2.5,c='b')
        ax2.tick_params(axis='x', labelsize=12)
        ax2.tick_params(axis='y', labelsize=12, labelcolor='b')
        fig.suptitle("Front Shape",fontsize='xx-large')
        plt.savefig(self.path + "/front_shape.png", dpi = 500)
        plt.close()

        #Plot actor learning curve
        ## ====================================================================================================================================================================================================== ##
        if(len(self.r_per_episode)!=0):
            plt.clf()
            plt.title("Actor Learning Curve, Episode-Wise",fontsize='xx-large')
            plt.xlabel("Episode",fontsize='large')
            plt.ylabel("Average Reward per Simulation Step [-]",fontsize='large')
            plt.plot([*range(len(self.r_per_episode))],self.r_per_episode,lw=2.5,c='r')
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.path + "/actor_learning.png", dpi = 500)
            plt.close()

        # Plot value learning curve
        ## ====================================================================================================================================================================================================== ##
        if(len(self.value_error)!=0):
            plt.clf()
            title_str = "Critic Learning Curve"
            plt.title(title_str,fontsize='xx-large')
            plt.xlabel("Optimization Step",fontsize='large')
            plt.ylabel("MSE Loss [-]",fontsize='large')
            plt.plot([*range(len(self.value_error))],self.value_error,lw=2.5,c='r')
            plt.yscale("log")
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.path + "/critic_learning.png", dpi = 500)
            plt.close()

        # Plot actor learning rate curve
        ## ====================================================================================================================================================================================================== ##
        if(len(self.actor_lr)!=0):
            plt.clf()
            title_str = "Actor Learning Rate"
            plt.title(title_str,fontsize='xx-large')
            plt.xlabel("Batch",fontsize='large')
            plt.ylabel("Alpha [-]",fontsize='large')
            plt.plot([*range(len(self.actor_lr))],self.actor_lr,lw=2.5,c='r')
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.path + "/actor_alpha.png", dpi = 500)
            plt.close()

        # Plot critic learning rate curve
        ## ====================================================================================================================================================================================================== ##
        if(len(self.actor_lr)!=0):
            plt.clf()
            title_str = "Critic Learning Rate"
            plt.title(title_str,fontsize='xx-large')
            plt.xlabel("Batch",fontsize='large')
            plt.ylabel("Alpha [-]",fontsize='large')
            plt.plot([*range(len(self.critic_lr))],self.critic_lr,lw=2.5,c='r')
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.path + "/critic_alpha.png", dpi = 500)
            plt.close()

        # x and y stdev curves
        ## ====================================================================================================================================================================================================== ##
        if(len(self.x_stdev)!=0):
            plt.clf()
            plt.title("X Rate Stdev, Episode-Wise",fontsize='xx-large')
            plt.xlabel("Episode",fontsize='large')
            plt.ylabel("X Rate Stdev [mm/s]",fontsize='large')
            plt.plot([*range(len(self.x_stdev))],1000.0*np.array(self.x_stdev),lw=2.5,c='r')
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.path + "/x_stdev.png", dpi = 500)
            plt.close()

        if(len(self.y_stdev)!=0):
            plt.clf()
            plt.title("Y Rate Stdev, Episode-Wise",fontsize='xx-large')
            plt.xlabel("Episode",fontsize='large')
            plt.ylabel("Y Rate Stdev [mm/s]",fontsize='large')
            plt.plot([*range(len(self.y_stdev))],1000.0*np.array(self.y_stdev),lw=2.5,c='r')
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.path + "/y_stdev.png", dpi = 500)
            plt.close()

        # Plot magnitude stdev curve
        ## ====================================================================================================================================================================================================== ##
        if(len(self.mag_stdev)!=0):
            plt.clf()
            plt.title("Magnitude Stdev, Episode-Wise",fontsize='xx-large')
            plt.xlabel("Episode",fontsize='large')
            plt.ylabel('Magnitude Stdev [KW/m^2-s]',fontsize='large')
            plt.plot([*range(len(self.mag_stdev))],0.001*np.array(self.mag_stdev),lw=2.5,c='r')
            plt.xticks(fontsize='large')
            plt.yticks(fontsize='large')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.path + "/mag_stdev.png", dpi = 500)
            plt.close()

    def render(self):
        print("Rendering simulation results...")

        # Determine fit space for front fit
        fit_y_coords = np.linspace(0.0, self.mesh_y_z0[0][-1], 100)

        # Determine temperature ranges to use for plotting
        min_temp = np.mean(self.cum_max_temp_field[-1]) - np.mean(self.cum_max_temp_field[-1]) % 0.05
        max_temp = round(np.max(self.cum_max_temp_field[-1]) + 0.05 - (np.max(self.cum_max_temp_field[-1]) % 0.05),2)
        max_temp = min(min_temp + 2*(np.std(self.cum_max_temp_field[-1]) - np.std(self.cum_max_temp_field[-1]) % 0.05), max_temp)

        for curr_step in range(len(self.time)):

            # Calculate input field
            input_percent = self.input_percent[curr_step]
            input_location_x = self.input_location_x[curr_step]
            input_location_y = self.input_location_y[curr_step]
            input_mesh = input_percent*self.max_input_mag*np.exp(((self.mesh_x_z0-input_location_x)**2*self.exp_const) +
               														   (self.mesh_y_z0-input_location_y)**2*self.exp_const)
            input_mesh[input_mesh<0.01*self.max_input_mag] = 0.0

            # Make fig for temperature, cure, and input
            plt.cla()
            plt.clf()
            fig, (ax0, ax1, ax2) = plt.subplots(3, 1)
            fig.set_size_inches(11,8.5)

            # Calculate fine mesh
            x_linspace = np.linspace(self.fine_mesh_loc[curr_step][0], self.fine_mesh_loc[curr_step][1], len(self.fine_cure_field[curr_step]))
            y_linspace = np.linspace(self.mesh_y_z0[0][0], self.mesh_y_z0[0][len(self.mesh_y_z0[0])-1], len(self.fine_cure_field[curr_step][0]))
            fine_mesh_y, fine_mesh_x = np.meshgrid(y_linspace, x_linspace)

            # Plot normalized temperature
            c0 = ax0.pcolormesh(1000.0*self.global_fine_mesh_x, 1000.0*self.global_fine_mesh_y, self.interpolated_temp_field[curr_step], shading='gouraud', cmap='jet', vmin=min_temp, vmax=max_temp)
            cbar0 = fig.colorbar(c0, ax=ax0)
            cbar0.set_label("ϴ [-]",labelpad=20,fontsize='large')
            cbar0.ax.tick_params(labelsize=12)
            ax0.set_xlabel('X Position [mm]',fontsize='large')
            ax0.set_ylabel('Y Position [mm]',fontsize='large')
            ax0.tick_params(axis='x',labelsize=12)
            ax0.tick_params(axis='y',labelsize=12)
            ax0.set_aspect('equal', adjustable='box')
            ax0.set_title('Front Temp = '+'{:.2f}'.format(self.front_temperature[curr_step]-273.15)+' C | '+
                          'Front Speed = '+'{:.2f}'.format(self.front_velocity[curr_step]*1000.0)+' mm/s | '+
                          'Front Shape = '+'{:.2f}'.format(self.front_shape_param[curr_step])+'\n',fontsize='large', fontname = 'monospace')

            # Plot cure
            c1 = ax1.pcolormesh(1000.0*self.mesh_x_z0, 1000.0*self.mesh_y_z0, self.cure_field[curr_step,:,:], shading='gouraud', cmap='YlOrBr', vmin=0.0, vmax=1.0)
            ax1.pcolormesh(1000.0*fine_mesh_x, 1000.0*fine_mesh_y, self.fine_cure_field[curr_step,:,:], shading='gouraud', cmap='YlOrBr', vmin=0.0, vmax=1.0)
            cbar1 = fig.colorbar(c1, ax=ax1)
            cbar1.set_label('α [-]', labelpad=20,fontsize='large')
            cbar1.ax.tick_params(labelsize=12)
            ax1.set_xlabel('X Position [mm]',fontsize='large')
            ax1.set_ylabel('Y Position [mm]',fontsize='large')
            ax1.tick_params(axis='x',labelsize=12)
            ax1.tick_params(axis='y',labelsize=12)
            ax1.set_aspect('equal', adjustable='box')
            ax1.set_title('Reward = '+'{:.2f}'.format(self.reward[curr_step,0]),fontsize='large', fontname = 'monospace')

            # Determine front locations based on front curve data
            front_x_location = self.front_curve[curr_step][0]
            front_y_location = self.front_curve[curr_step][1]
            front_x_location = front_x_location[front_x_location >= 0.0]
            front_y_location = front_y_location[front_y_location >= 0.0]
            front_x_location = 1000.0*front_x_location
            front_y_location = 1000.0*front_y_location
            front_instances = len(front_x_location)

            # Determine front locations based on fit data
            fit_x_coords = np.zeros(len(fit_y_coords))
            for order in range(len(self.front_fit[curr_step])):
                fit_x_coords = fit_x_coords + self.front_fit[curr_step][order] * (fit_y_coords**order)

            # Plot input
            c2 = ax2.pcolormesh(1000.0*self.mesh_x_z0, 1000.0*self.mesh_y_z0, 1.0e-3*input_mesh, shading='gouraud', cmap='coolwarm', vmin=0.0, vmax=1.0e-3*self.max_input_mag)
            if front_instances != 0:
                ax2.plot(front_x_location, front_y_location, 's', color='black', markersize=2.25, markeredgecolor='black')
                ax2.plot(1000.0*fit_x_coords, 1000.0*fit_y_coords, lw=2.0, c='m')
            ax2.axvline(1000.0*self.fine_mesh_loc[curr_step][0], color='red', alpha=0.70, ls='--')
            ax2.axvline(1000.0*self.fine_mesh_loc[curr_step][1], color='red', alpha=0.70, ls='--')
            cbar2 = fig.colorbar(c2, ax=ax2)
            cbar2.set_label('Input Heat [KW/m^2]',labelpad=20,fontsize='large')
            cbar2.ax.tick_params(labelsize=12)
            ax2.set_xlabel('X Position [mm]',fontsize='large')
            ax2.set_ylabel('Y Position [mm]',fontsize='large')
            ax2.set_xlim(0.0, 1000.0*self.mesh_x_z0[-1][0])
            ax2.set_ylim(0.0, 1000.0*self.mesh_y_z0[0][-1])
            ax2.tick_params(axis='x',labelsize=12)
            ax2.tick_params(axis='y',labelsize=12)
            ax2.set_aspect('equal', adjustable='box')
            if self.max_input_mag > 0.0:
                ax2.set_title('Input Power = '+'{:.2f}'.format(self.input_percent[curr_step]*100.0)+' %' ,fontsize='large', fontname = 'monospace')
            else:
                ax2.set_title('Input Power = '+'{:.2f}'.format(0.00)+' %' ,fontsize='large', fontname = 'monospace')

            # Set title and save
            title_str = "Time From Trigger: "+'{:.2f}'.format(self.time[curr_step])+'s'
            fig.suptitle(title_str,fontsize='xx-large', fontname = 'monospace')
            plt.savefig(self.video_1_path+str(curr_step).zfill(4)+'.png', dpi=100)
            plt.close()

            # Get the trimmed temperature and cure curves for the front curve plotting
            fine_front_temp_curve = self.fine_temperature_field[curr_step][:,len(self.fine_temperature_field[curr_step][0])//2]
            fine_front_cure_curve = self.fine_cure_field[curr_step][:,len(self.fine_cure_field[curr_step][0])//2]
            fine_x_coords = np.linspace(self.fine_mesh_loc[curr_step][0], self.fine_mesh_loc[curr_step][1], len(self.fine_temperature_field[0,:,0]))
            beg_plot = max((self.mean_front_x_locations[curr_step] - 0.40*(self.fine_mesh_loc[curr_step][1]-self.fine_mesh_loc[curr_step][0])), 0.0)
            end_plot = min((self.mean_front_x_locations[curr_step] + 0.40*(self.fine_mesh_loc[curr_step][1]-self.fine_mesh_loc[curr_step][0])), self.mesh_x_z0[-1,0])

            # Set up front plot
            plt.cla()
            plt.clf()
            fig, ax1 = plt.subplots()
            fig.set_size_inches(8.5,5.5)

            # Plot front temperature
            ax1.set_xlabel("X Location [mm]",fontsize='large')
            ax1.set_ylabel("α [-]",fontsize='large',color='r')
            ax1.plot(1000.0*fine_x_coords, fine_front_cure_curve, c='r', lw=2.5)
            ax1.set_ylim(-0.1, 1.1)
            ax1.set_xlim(1000.0*beg_plot, 1000.0*end_plot)
            ax1.tick_params(axis='x', labelsize=12)
            ax1.tick_params(axis='y', labelsize=12, labelcolor='r')

            # Plot front cure
            ax2 = ax1.twinx()
            ax2.set_ylabel("ϴ [-]",fontsize='large',color='b')
            ax2.plot(1000.0*fine_x_coords, fine_front_temp_curve, c='b', lw=2.5)
            ax2.set_ylim(-0.1, 1.1)
            ax2.set_xlim(1000.0*beg_plot, 1000.0*end_plot)
            ax2.tick_params(axis='x', labelsize=12)
            ax2.tick_params(axis='y', labelsize=12, labelcolor='b')

            # Save and close figure
            title_str = "Time From Trigger: "+'{:.2f}'.format(self.time[curr_step])+'s'
            fig.suptitle(title_str,fontsize='xx-large', fontname = 'monospace')
            plt.gcf().set_size_inches(8.5, 5.5)
            plt.savefig(self.video_2_path+str(curr_step).zfill(4)+'.png', dpi=100)
            plt.close()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Feb 23 13:59:13 2019

Note:
coba plot antara syntethic vs true

_______________________________________________________________________________

author:
    @71unxv
    hibatullah.geophysics@gmail.com
    github.com/71unxv
    linkedin.com/in/irsyadhbtlh/

Cheers!
"""
###############################################################################
# Import Library
import numpy as np
import matplotlib.pyplot as plt
import lasio as ls

###############################################################################
# Function
def createRC(Z):
    RC = (Z[1:] - Z[:-1]) / (Z[1:] + Z[:-1])
    RC = np.append([0],RC)
    return RC

def ricker(f, length, dt):
    t = np.linspace(-length / 2, (length-dt) / 2, length / dt)
    y = (1. - 2.*(np.pi**2)*(f**2)*(t**2))*np.exp(-(np.pi**2)*(f**2)*(t**2))
    return y
###############################################################################
# Run code! Run!!

well_raw = ls.read('5tieup/Kapuni-8/Wireline/kapuni-8_final.las')
well_data=well_raw.df()
DEPTH_ALL=np.array(well_data.index)
DENS = np.array(well_data[well_data["DENS"].notnull() & well_data["DTC"].notnull()]["DENS"])
SLOW  = np.array(well_data[well_data["DENS"].notnull() & well_data["DTC"].notnull()]["DTC"])
DEPTH = np.array(well_data[well_data["DENS"].notnull() & well_data["DTC"].notnull()].index)
###########################################################################
# load check shot

chk = np.loadtxt('chk-kapuni-8.txt')
chk_depth =chk[:,0]
chk_time  =chk[:,1]

#############################################################################
# load seismic traces

seis = np.loadtxt('trace_seismik_kapuni8-1.dat')
seis_time =seis[:,0]/1000
seis_amplitude  =seis[:,1]

depth_sample = 0.1524
time_sample = 0.002
time_length = 3
ft2m = 0.3048

##############################################################################
# get the T0 value
chk_func = np.poly1d(np.polyfit(chk_depth, chk_time, 2))

T0=chk_func(DEPTH[0])

############################################################################
# calculate Z dan RC

Z = SLOW * DENS
RC = createRC(Z)

######################################################################33
# create synthetic seismic
DT = SLOW * depth_sample *(1/304800)

TIME = 2* np.cumsum(DT)
TIME = TIME + (2*T0)

TIME_resample = np.arange(0, time_length, time_sample)

Z_resample = np.interp(TIME_resample, TIME, Z)
RC_resample = createRC(Z_resample)

wavelet = ricker(20, 0.512, 0.001)
syn_seismic = np.convolve(RC_resample, wavelet, mode='same')

##############################################################################
# create time by sonic log
well_tie_func = np.poly1d(np.polyfit(DEPTH, TIME, 5))

TIME_OUT=well_tie_func(DEPTH_ALL)

############################################################################
# plot the well
fig01=plt.figure()

fig01.add_subplot(171)

fig01.add_subplot(171).plot(DENS,-TIME)
fig01.add_subplot(171).set_ylim(-2.1,-1.4)

fig01.add_subplot(172)
fig01.add_subplot(172).plot(SLOW,-TIME)
fig01.add_subplot(172).set_ylim(-2.1,-1.4)

fig01.add_subplot(173)
fig01.add_subplot(173).plot(Z,-TIME)
fig01.add_subplot(173).set_ylim(-2.1,-1.4)

fig01.add_subplot(174)
fig01.add_subplot(174).plot(RC,-TIME)
fig01.add_subplot(174).set_ylim(-2.1,-1.4)

fig01.add_subplot(175)
fig01.add_subplot(175).plot(RC_resample,-TIME_resample)
fig01.add_subplot(175).set_ylim(-2.1,-1.4)

fig01.add_subplot(176)
fig01.add_subplot(176).plot(syn_seismic,-TIME_resample)
fig01.add_subplot(176).set_ylim(-2.1,-1.4)

fig01.add_subplot(177)
fig01.add_subplot(177).plot(seis_amplitude,-seis_time)
fig01.add_subplot(177).set_ylim(-2.1,-1.4)

#correlation_seis=np.corrcoef(syn_seismic,seis_amplitude[:1500])
#fig01.add_subplot(177).set_xlim(-4000,4000)
fig01.show()

#fig02=plt.figure()
#
#fig02.add_subplot(111).plot(DEPTH,TIME,'.')
#fig02.show()


#####Encapsulation#####

class Person():
    def __init__(self, name):
        self.name = name
        self.__education = 'Engineering'    #Private variable

    def displayInfo(self):
        name = self.name
        education = self.__education
        print('My name is ', name, 'and I completed my ', education)

myObj = Person('Omkar')
myObj.displayInfo()
print(myObj.name)                   #Can be accessed
#print(myObj.__education)            #Throws an error
print(myObj._Person__education)     #Private variable is accessed like this


import logging
import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from pathlib import Path

from RedStakeGUI.constants import SETTINGS_MANAGER

class QuoteEmail:
    """Class to send email with quote attached."""

    def __init__(self, inputs: dict, parcel_data: dict, quote_file_path: str):
        self.inputs = inputs
        self.parcel_data = parcel_data
        self.quote_file_path = quote_file_path
        (
            self.sender,
            self.receiver,
            self.password,
        ) = SETTINGS_MANAGER.get_email_settings()
        self.subject = self.create_subject()
        self.message = self.create_message()

    def create_subject(self) -> str:
        """This method will create the subject for the email.

        Returns:
            str: The subject for the email.
        """
        subject_prefix = ""
        file_number = self.inputs["Red Stake File Number"].get().strip()
        address = self.parcel_data.get("PRIMARY_ADDRESS", "")
        if file_number and address[0].isnumeric():
            subject_prefix = f"FN {file_number} #"
        elif file_number:
            subject_prefix = f"FN {file_number} "

        subject = self.parcel_data.get("PRIMARY_ADDRESS", "")
        subject_suffix = " - (Survey Quote Request)"

        return (subject_prefix + subject + subject_suffix).strip()

    def create_message(self) -> str:
        """This method will create the message for the email.

        Returns:
            str: The message for the email.
        """
        address = self.parcel_data.get("PRIMARY_ADDRESS", "")
        parcel_id = self.parcel_data.get("PARCEL_ID", "")
        scope_of_work = self.inputs["Scope of Work"].get().strip()
        additional_info = self.inputs["Additional Information"].get().strip()
        parcel_links = self.parcel_data.get("LINKS", "")

        appraiser = parcel_links.get("PROPERTY_APPRAISER", "")
        appraiser_map = parcel_links.get("MAP", "")
        deed = parcel_links.get("DEED", "")
        file_number = self.inputs["Red Stake File Number"].get().strip()

        if additional_info:
            additional_info = "\nAdditional Info = " + additional_info

        if appraiser_map:
            appraiser_map = "\nMap = " + appraiser_map

        if deed:
            deed = "\nDeed = " + deed

        if file_number:
            file_number = f"File Number = {file_number}\n"

        plat = ""
        if self.parcel_data.get("PLAT_BOOK"):
            plat = "\nPlat = " + parcel_links.get("PLAT", "")

        message = f"""Hello,

See attached files for supporting data on this quote.

{file_number}Address = {address}
Parcel ID = {parcel_id}
Requested services = {scope_of_work}{additional_info}

Property Appraiser = {appraiser}{appraiser_map}{deed}{plat}

Thank you"""

        return message

    def send_email(self) -> None:
        """This method will send the email with the quote attached."""
        msg = MIMEMultipart()
        msg["Subject"] = self.subject
        msg["From"] = self.sender
        msg["To"] = self.receiver

        msg.attach(MIMEText(self.message, "plain"))

        filename = self.quote_file_path

        with open(filename, "rb") as attachment:
            part = MIMEBase("application", "octet-stream")
            part.set_payload(attachment.read())

        encoders.encode_base64(part)

        part.add_header(
            "Content-Disposition",
            f"attachment; filename= {Path(filename).name}",
        )

        msg.attach(part)
        text = msg.as_string()

        with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp_server:
            try:
                logging.info("Logging into Gmail.")
                logging.info(f"Sender: {self.sender}")
                logging.info(f"Receiver: {self.receiver}")
                smtp_server.login(self.sender, self.password.decode())
            except smtplib.SMTPAuthenticationError:
                logging.error("Failed to log into Gmail.")
                return

            try:
                smtp_server.sendmail(self.sender, self.receiver, text)
                logging.info("Successfully sent email.")
            except smtplib.SMTPRecipientsRefused:
                logging.error("Failed to send email.")
                return


from pathlib import Path
from typing import List, AnyStr

from cgcrepair.utils.data import Test

class Tests:
    def __init__(self, polls_path: Path, povs_path: Path, tests: List[AnyStr] = None, pos_tests: bool = False,
                 neg_tests: bool = False, only_numbers: bool = False):
        self.polls_path = polls_path
        self.povs_path = povs_path
        self.only_numbers = only_numbers
        self.pos_tests = {}
        self.neg_tests = {}
        self.mapped = {}
        self._tests = {}
        self.load_pos_tests()
        self.load_neg_tests()
        self.map_only_number_ids()

        if tests:
            self._tests = {test: self[test] for test in tests}
        elif pos_tests:
            self._tests = self.pos_tests
        elif neg_tests:
            self._tests = self.neg_tests
        else:
            self._tests.update(self.pos_tests)
            self._tests.update(self.neg_tests)

    def __getitem__(self, name: str):
        if self.only_numbers:
            if name in self.mapped:
                return self.mapped[name]
        elif name in self.pos_tests:
            return self.pos_tests[name]
        elif name in self.neg_tests:
            return self.neg_tests[name]

        raise ValueError(f"Test {name} not found in set of tests.")

    def __iter__(self):
        return iter(self._tests)

    def __len__(self):
        return len(self._tests)

    def keys(self):
        return self._tests.keys()

    def items(self):
        return self._tests.items()

    def values(self):
        return self._tests.values()

    def load_pos_tests(self):
        if not self.pos_tests:
            # Map cases to tests names where p is for positive test cases
            for i, file in enumerate(sorted(self.get_polls()), 1):
                test = Test(name=f"p{i}", is_pov=False, file=file, order=i)
                self.pos_tests[test.name] = test

            if not self.pos_tests:
                raise ValueError('No polls found')

    def load_neg_tests(self):
        if not self.neg_tests:
            # Map cases to tests names where n is for negative test cases
            for i, file in enumerate(sorted(self.get_povs()), 1):
                test = Test(name=f"n{i}", is_pov=True, file=file, order=i)
                self.neg_tests[test.name] = test

            if not self.neg_tests:
                raise ValueError('No POVs found')

    def get_polls(self):
        for_release = self.polls_path / Path('for-release')
        for_testing = self.polls_path / Path('for-testing')

        if for_testing.exists():
            return [file for file in for_testing.iterdir() if file.suffix == ".xml"]

        if for_release.exists():
            return [file for file in for_release.iterdir() if file.suffix == ".xml"]

    def get_povs(self):
        return [file for file in self.povs_path.iterdir() if file.suffix == ".pov"]

    def map_only_number_ids(self):
        if not self.mapped:
            self.mapped = {str(test.order): test for test in self.pos_tests.values()}
            count_pos_tests = len(self.pos_tests)
            self.mapped.update({str(test.order + count_pos_tests): test for test in self.neg_tests.values()})

            if not self.mapped:
                raise ValueError("Input tests could not be mapped with available tests.")


import os
mydict={}
myfiles=[]
for RootDir, SubDir, Files in os.walk('/home/sandeep.m/test'):
    for file in Files:
        abspath=os.path.join(RootDir,file)
        inode=os.stat(abspath).st_ino
        if inode not in mydict:
            mydict[inode]=myfiles
        else:
            myfiles.append(abspath)
print(mydict)
#print(mydict.values())
#print(mydict.keys())


# Ejemplo de como hacer cual es el numero mas grande y cual es mas pequeno

print("Dime {} tres numeros y te dire cual es el mas pequenio y cual el mas grande.".format(nombre))
numero1 = int(input("Dime tu primer numero: "))
numero2 = int(input("Dime tu segundo numero: "))
numero3 = int(input("Dime tu cercer numero "))
print("El numero mas pequenio es {}".format(min(numero1,numero2,numero3)))
print("El numero mas grande es {}".format(max(numero1,numero2,numero3)))

exit = input("Pulsa cualquier tecla para salir..")
f.close()


from django.shortcuts import render, redirect
from .forms import FirewallRequestForm, FirewallRuleForm
from .models import FirewallRule

# Create your views here.
def firewall(request):

    if request.method == "POST":
        firewallrequest_from = FirewallRequestForm(request.POST)
        firewallrule_form = FirewallRuleForm(request.POST)

        if firewallrequest_from.is_valid() and firewallrule_form.is_valid():
            r = firewallrequest_from.save()

            firewallrequest = r.request_id
            source_ip = firewallrule_form["source_ip"].cleaned_data()
            source_host = firewallrule_form["source_host"].cleaned_data()
            source_port = firewallrule_form["source_port"].cleaned_data()
            destination_ip = firewallrule_form["destination_ip"].cleaned_data()
            destination_host = firewallrule_form["destination_host"].cleaned_data()
            destination_port = firewallrule_form["destination_port"].cleaned_data()
            protocol = firewallrule_form["destination_protocol"].cleaned_data()

            rule = FirewallRule(
                firewallrequest=firewallrequest,
                source_ip=source_ip,
                source_host=source_host,
                source_port=source_port,
                destination_ip=destination_ip,
                destination_host=destination_host,
                destination_port=destination_port,
                protocol=protocol,
            )
            rule.save()

            return redirect(
                request,
                "/firewall.html",
                {
                    "firewallrequest_form": firewallrequest_from,
                    "firewallrule_form": firewallrule_form,
                },
            )
    else:
        firewallrequest_from = FirewallRequestForm()
        firewallrule_form = FirewallRuleForm()
    return render(
        request,
        "firewall/firewall.html",
        {
            "firewallrequest_form": firewallrequest_from,
            "firewallrule_form": firewallrule_form,
        },
    )


from collections import Counter
import json
from pathlib import Path
from operator import itemgetter

import numpy as np
import pandas as pd

from utils import state2pos

def snapshot_log(episode, env, agent, tr, log_dict, debug=True):
    # Read step variables
    actions = env.action_set
    action_index = actions.index(tuple(tr[1]))
    step_count = env.step_count
    total_step_count = agent.step_count
    reward = np.mean(tr[2])

    log_dict["state"].append(int(tr[0]))
    log_dict["action"].append(action_index)
    log_dict["reward"].append(reward)
    log_dict["step_count"].append(step_count)
    log_dict["episode"].append(episode)

    if "task" not in log_dict:
        log_dict["task"] = agent.task
    if "label" not in log_dict:
        log_dict["label"] = agent.label

    if "average_reward" not in log_dict:
        avg_reward = reward
    else:
        prev = log_dict["average_reward"][-1]
        avg_reward = (prev * (total_step_count - 1) + reward) / total_step_count
    log_dict["average_reward"].append(avg_reward)

    step_log = (
        f"TRAIN: Episode: {episode}\t"
        f"Steps: {step_count}\t"
        f"Average Reward: {avg_reward:0.4f}\t"
    )

    if hasattr(agent, "epsilon"):
        log_dict["epsilon"].append(np.round(agent.epsilon, 4))

    if hasattr(agent, "tau"):
        log_dict["tau"].append(np.round(agent.tau, 4))
        step_log += f'tau: {log_dict["tau"][-1]:0.4f}\t'

    if hasattr(agent, "mu"):
        log_dict["mu"].append(np.mean(agent.mu))
        step_log += f"Globally Averaged J: {agent.mu:0.4f}\t"

    if hasattr(agent, "delta"):
        if hasattr(agent.delta, "__iter__"):
            log_dict["delta"].append([float(np.round(di, 4)) for di in agent.delta])
        else:
            log_dict["delta"].append(float(np.round(agent.delta, 4)))

    if debug:
        n_actions = len(actions)
        states_positions_gen = env.next_states()
        try:
            while True:
                state, _ = next(states_positions_gen)
                log_dict[f"V({state})"].append(agent.V[state])

                log_dict[f"PI({state})"].append(agent.PI[state])

                if hasattr(agent, "Q"):
                    log_dict[f"Q({state})"].append(agent.Q[state, :].tolist())

                if hasattr(agent, "A"):
                    log_dict[f"A({state})"].append(agent.A[state, :].tolist())

        except StopIteration:
            pass

    return step_log

def snapshot_state_actions_log(log, log_dir=None):
    sa = list(zip(log["state"], log["action"]))

    # TODO: Ideal build a dataframe from dictionary
    sac = [k + (v,) for k, v in Counter(sa).items()]

    df = pd.DataFrame.from_records(sac, columns=["state", "action", "count"]).pivot(
        index="state", columns="action", values="count"
    )

    sa_path = Path(log_dir) / "state_action.csv"
    if log_dir is not None:
        df.to_csv(sa_path)
    return df

def snapshot_paths(log_dir, log=None, max_k_paths=10):
    """Builds a transiton counter"""
    log_path = log_dir / "snapshot.json"
    if log is None:
        with log_path.open("r") as f:
            log = json.load(f)
    paths = []
    cur = -1
    first = True
    for episode, state in zip(log["episode"], log["state"]):
        if episode != cur:
            if not first:
                paths.append(tuple(episode_path))
            episode_path = []
            first = False
        episode_path.append(state)
        cur = episode
    paths.append(tuple(episode_path))

    paths_count = Counter(paths).items()
    paths_count = sorted(paths_count, key=itemgetter(1), reverse=True)
    paths_count = paths_count[:max_k_paths]
    paths, count = zip(*paths_count)
    df = pd.DataFrame.from_records(paths, index=count)

    # Format state into positions
    def fn(x):
        return [str(tup) for tup in map(tuple, state2pos(x))]

    def gn(x):
        return np.nan if np.isnan(x) else ",".join(fn(x))

    df = df.applymap(gn)

    paths_path = Path(log_dir) / "paths.csv"
    if log_dir is not None:
        df.to_csv(paths_path.as_posix())
    return df

if __name__ == "__main__":
    pass


"""Runs a test server.

Example usage:
  python run_server.py --env test --port 9001
"""

import argparse

import ruddock
from ruddock import app

parser = argparse.ArgumentParser(
    description="Run a local instance of the test server.")
parser.add_argument("--env", default="dev",
    help="Environment to run application in. Can be 'prod', 'dev', or 'test'. "
        + "Default is 'dev'.")
parser.add_argument("--port", type=int, default=5000,
    help="Port to attach application to. Default is 5000.")

if __name__ == "__main__":
  args = parser.parse_args()
  ruddock.init(args.env)
  app.run(port=args.port)