__author__ = "Giacomo Cristinelli, José A. Iglesias and Daniel Walter"
__date__ = "November 1st, 2023"

import argparse
import configparser
import time
from datetime import datetime

import maxflow
import numpy as np
import os
import scipy.sparse as cp
from dolfin import *
from matplotlib import pyplot as pp
from mshr import *

from GCG_operations import _sparsify, _Dinkelbach, _symmetric_rectangle_mesh, plot_result, TV
from SSN import _SSN

# ----------------------------------------------------------------------------------------------------------------------
def _main_():
    # ---CREATING DIRECTORY AND LOG FILE ------------------------------------------------------------------------------
    # FEniCS log level
    set_log_level(30)

    now = datetime.now()
    dt_string = now.strftime("%y%m%d_%H%M%S")
    rd = os.path.join(os.path.dirname(__file__), './results/' + dt_string)
    if not os.path.isdir(rd):
        os.makedirs(rd)
    flog = open(rd + '/log.txt', "w")

    # ---TIMER
    start_time = time.time()

    # A1.---STORING INFO IN THE LOG FILE
    flog.write("Variables:\n")

    if Random_mesh and d == 2:
        flog.write("  Random mesh in a rectangle of size {}x{}, with indicator {}\n".format(lx2 - lx1, ly2 - ly1, N))
    elif Random_mesh and d == 3:
        flog.write(
            "  Random mesh in a cube of size {}x{}x{}, with indicator {}\n".format(lx2 - lx1, ly2 - ly1, lz2 - lz1, N2))
    elif not Random_mesh and d == 2:
        flog.write("  Standard crossed mesh with {}x{} squares in a rectangle of size {}x{}\n".format(Nx, Ny, lx2 - lx1,
                                                                                ly2 - ly1))
    elif not Random_mesh and d == 3:
        flog.write("  Regular Box mesh with {}x{}x{} cubes in a cube of size {}x{}x{}\n".format(Nx, Ny, Nz, lx2 - lx1,
                                                                                ly2 - ly1, lz2 - lz1))
    else:
        raise Exception("Not supported dimension. Choose d=2,3")

    flog.write("  The dimension is {}\n".format(d))
    flog.write("  The regularizer parameter is {}\n".format(alpha))
    flog.write("  TV with boundary: {}.\n".format(boundary))
    flog.write("  GCG has tolerance {}, and {} max iterations.\n".format(tolerance, max_iterations))

    # ---GEOMETRY LOOP ------------------------------------------------------------------------------------------------

    flog.write("Geometry loop:\n")
    print("Starting geometry loop...\n")

    # B1.---MESH GENERATION AND FUNCTION SPACES
    if Random_mesh and d == 2:
        mesh = _symmetric_rectangle_mesh(lx2, ly2, N)
    elif Random_mesh and d == 3:
        domain = Box(Point(lx1, ly1, lz1), Point(lx2, ly2, lz2))
        mesh = generate_mesh(domain, N2)
    elif not Random_mesh and d == 2:
        mesh = RectangleMesh(Point(lx1, ly1), Point(lx2, ly2), Nx, Ny, 'crossed')
    else:
        mesh = BoxMesh(Point(lx1, ly1, lz1), Point(lx2, ly2, lz2), Nx, Ny, Nz)

    V = FunctionSpace(mesh, 'DG', 0)  # PWC
    VL = FunctionSpace(mesh, 'CG', 1)  # PWL
    mesh.init()
    mesh.init(d - 1, d)
    e2f = mesh.topology()(d - 1, d)
    vol_face_fn = Function(V)
    bdy_length_fn = Function(V)
    int_lengths = np.empty(0)
    bdy_length = np.empty(0)
    int_cells = np.empty(shape=[0, 2])
    bdy_faces = np.empty(0)
    internal_facets = np.empty(0)
    facet_size = np.empty(0)
    bdy_facets = np.empty(0)

    if d == 2:
        flog.write("  Made a mesh with {} vertices, and {} faces \n".format(mesh.num_vertices(), mesh.num_faces()))
        plot(mesh, linewidth=0.25)
        pp.savefig(rd + '/0_mesh.png', bbox_inches='tight', dpi=300)
        pp.close()
    elif d == 3:
        flog.write(
            "  Made a mesh with {} vertices, {} faces, and {} {}-dimensional cells \n".format(mesh.num_vertices(),
                                                                                mesh.num_faces(),
                                                                                mesh.num_cells(), d))
    # dividing into boundary or internal facets
    facets_list = np.arange(mesh.num_facets())
    bdy_facets = np.array([facet for facet in facets_list if len(e2f(facet)) == 1], dtype=int)
    internal_facets = np.setdiff1d(facets_list, bdy_facets)

    # Defining dimension dependent size of facets
    if d == 2:
        facet_size = np.array([Edge(mesh, edge).length() for edge in facets_list])
    elif d == 3:
        facet_size = np.array([Face(mesh, face).area() for face in facets_list])

    int_cells = np.array([[facet, e2f(facet)[0], e2f(facet)[1]] for facet in internal_facets], dtype=int)
    int_lengths = facet_size[internal_facets]
    vol_face_fn.vector()[:] = [Cell(mesh, cell).volume() for cell in range(mesh.num_cells())]
    mid_cell = [Cell(mesh, cell).midpoint().array() for cell in range(0, mesh.num_cells())]

    # Process boundary info if needed
    if boundary:
        for facet in bdy_facets:
            bdy_length_fn.vector()[e2f(facet)[0]] += facet_size[facet]
            bdy_faces = np.append(bdy_faces, e2f(facet)[0])
            bdy_length = np.append(bdy_length, facet_size[facet])

    # B2.---GRAPH GENERATION, creating graph with (d-1)-facets areas/length as weights
    G = maxflow.GraphFloat()
    G.add_nodes(mesh.num_cells())
    G.add_edges(int_cells[:, 1], int_cells[:, 2], facet_size[int_cells[:, 0]], facet_size[int_cells[:, 0]])

    flog.write("  Constructed graph has {} nodes, and {} edges \n".format(G.get_node_count(), G.get_edge_count()))
    flog.write("  Making the mesh and graph took - %.2f seconds \n" % (time.time() - start_time))

    print("Making the mesh of {} vertices, {} {}-dimensional cells, and the graph took - {} seconds \n".format(
        mesh.num_vertices(), mesh.num_cells(), d, time.time() - start_time))

    # ---PDE AND OPERATORS CONSTRUCTION--------------------------------------------------------------------------------

    # C1.---PDE INFO
    g = Constant(0.0)
    bdr = DirichletBC(VL, g, DomainBoundary())
    v = TestFunction(VL)
    u = TrialFunction(VL)
    mass_form = v * u * dx
    M = assemble(mass_form)
    mat = as_backend_type(M).mat()
    ai, aj, av = mat.getValuesCSR()
    M = cp.csr_matrix((av, aj, ai))

    """# C2.---REFERENCE CONTROL AND OBSERVATIONS FOR CIRCLES
    Lap = 1 * inner(grad(u), grad(v)) * dx + 0.5 * v * u * dx
    U0 = interpolate(Constant(1.0), V)
    u0 = U0 * v * dx
    U0_arr = U0.vector().get_local()
    Yd = Function(VL)
    UD = Function(V)
    Y0 = Function(VL)
    shift = np.ones((mesh.num_cells(), d))

    if d == 2:
        shift = np.hstack([shift, np.zeros((mesh.num_cells(), 1))])

    UD.vector()[:] = 2 * (np.linalg.norm(mid_cell + 0.33 * shift, axis=1) < sqrt(0.1)) + (
            np.linalg.norm(mid_cell + (-0.33) * shift, axis=1) < sqrt(0.15)) - 1

    energy_D = alpha * TV(mesh, vol_face_fn, bdy_length_fn, int_lengths, int_cells, bdy_length, bdy_faces, UD)

    flog.write("Energy of the toy control is %.8e\n" % energy_D)

    ud = UD * v * dx

    solve(Lap == ud, Yd, bdr, solver_parameters={'linear_solver': 'mumps'})
    solve(Lap == u0, Y0, bdr, solver_parameters={'linear_solver': 'mumps'})
    measurements = Yd.vector().get_local()

    plot_result(mesh, int_cells, flog, rd, project(Yd, V), 0, 0, d)
    plot_result(mesh, int_cells, flog, rd, UD, 2, "o", d)"""

    # C2.---REFERENCE CONTROL AND OBSERVATIONS FOR CASTLE
    Lap = 1 * inner(grad(u), grad(v)) * dx
    U0 = interpolate(Constant(1.0), V)
    u0 = U0 * v * dx
    U0_arr = U0.vector().get_local()
    if d == 2:
        Yd = interpolate(Expression('''x[0]>-0.5&&x[0]<0.5&&x[1]>-0.5&&x[1]<0.5? 1.00001:0.0''', degree=0), V)
    else:
        Yd = interpolate(
            Expression('''x[0]>-0.5&&x[0]<0.5&&x[1]>-0.5&&x[1]<0.5&&x[2]>-0.5&&x[2]<0.5? 1.00001:0.0''', degree=0), V)

    plot_result(mesh, int_cells, flog, rd, Yd, 0, 0, d)

    Yd = interpolate(Yd, VL)

    Y0 = Function(VL)
    solve(Lap == u0, Y0, bdr, solver_parameters={'linear_solver': 'mumps'})
    measurements = Yd.vector().get_local()

    # C3.---COEFFICIENTS, EXTREMALS, STATES
    Km = np.reshape(Y0.vector().get_local(), (-1, 1))
    Kl = np.empty(shape=[len(Km), 0])
    mean = np.array([1])
    coefficients = np.empty(0)
    Ul = np.empty(shape=[len(U0_arr), 0])

    # --GENERALIZED CONDITIONAL GRADIENT-------------------------------------------------------------------------------

    # D1.---WARM UP ITERATION
    coefficients, mean, adjoint, optval, misfit = _SSN(Kl, Km, coefficients, mean, measurements, alpha, M)
    flog.write("Warm up iteration:\n")
    flog.write("  The mean is {} \n".format(mean))
    flog.write("  The coefficients are {} \n".format(coefficients))
    flog.write("  The adjoint is {}\n".format(adjoint))
    flog.write("  Current value is %.6e \n" % optval)

    j = 0
    Uk = Function(V)
    prev_Uk = Function(V)
    Uk.vector()[:] = mean.flatten() * U0_arr + Ul @ coefficients
    opt = np.zeros(max_iterations + 1)
    energy = np.zeros(max_iterations + 1)
    rel_change = np.zeros(max_iterations + 1)
    data = []
    total_time = 0

    # D2.---MAIN LOOP
    while (j == 0) or (j <= max_iterations and opt[j - 1] > tolerance):
        start_iteration_time = time.time()
        flog.write("Iteration {} of Conditional gradient: \n".format(j))
        print("Starting iteration %s of GCG\n" % j)
        # Solve state and adjoint equation
        rhk = Uk * v * dx
        Yk = Function(VL)
        Vk = Function(VL)
        Vkc = Function(VL)
        prev_time = time.time()
        prev_Uk = Uk
        solve(Lap == rhk, Yk, bdr, solver_parameters={'linear_solver': 'mumps'})
        flog.write("  First PDE solve took %.2f seconds \n" % (time.time() - prev_time))
        Pk = Function(VL)
        rhpk = (Yk - Yd) * v * dx
        prev_time = time.time()
        solve(Lap == rhpk, Pk, bdr, solver_parameters={'linear_solver': 'mumps'})
        flog.write("  Second PDE solve took %.2f seconds \n" % (time.time() - prev_time))

        # making sure pk has zero average
        Pkp = project(Pk, V)
        intergral_pk = assemble(Pkp * dx)
        volume_domain = assemble(interpolate(Constant(1.0), V) * dx)
        Pkv = np.subtract(Pkp.vector(), intergral_pk / volume_domain)
        Pkp.vector()[:] = Pkv[:]
        flog.write("  Average of pk was %.6e \n" % assemble(Pkp * dx))

        if boundary:
            vm, extm, perm = _Dinkelbach(mesh, vol_face_fn, bdy_length_fn, int_lengths, int_cells, bdy_length,
                                         bdy_faces, G, Pkp, 1, alpha, flog, j, tolerance=tolerance)
            vp, extp, perp = _Dinkelbach(mesh, vol_face_fn, bdy_length_fn, int_lengths, int_cells, bdy_length,
                                         bdy_faces, G, Pkp, -1, alpha, flog, j, tolerance=tolerance)
            flog.write("  The new extremal coefficients are {}, {} \n".format(extm, extp))
            if np.abs(extp) > np.abs(extm):
                rhvk = -(vp / perp) * v * dx
                Ul = np.append(Ul, np.reshape(-vp.vector().get_local() / perp, (-1, 1)), axis=1)
            else:
                rhvk = (vm / perm) * v * dx
                Ul = np.append(Ul, np.reshape(vm.vector().get_local() / perm, (-1, 1)), axis=1)
        else:
            vm, extm, perm = _Dinkelbach(mesh, vol_face_fn, bdy_length_fn, int_lengths, int_cells, bdy_length,
                                         bdy_faces, G, Pkp, 1, alpha, flog, j, tolerance=tolerance)
            flog.write("  The new extremal coefficient is {} \n".format(extm))
            if perm < tolerance:
                raise Exception("Zero cut at iteration %s" % j)
            plot_result(mesh, int_cells, flog, rd, vm, 1, j, d)
            rhvk = (vm / perm) * v * dx
            Ul = np.append(Ul, np.reshape(vm.vector().get_local() / perm, (-1, 1)), axis=1)

        prev_time = time.time()
        solve(Lap == rhvk, Vk, bdr, solver_parameters={'linear_solver': 'mumps'})
        flog.write("  Last PDE solve took %.2f seconds\n" % (time.time() - prev_time))
        Kl = np.append(Kl, np.reshape(Vk.vector().get_local(), (-1, 1)), axis=1)
        coefficients = np.append(coefficients, np.array([1]))
        coefficients, mean, adjoint, optval, misfit = _SSN(Kl, Km, coefficients, mean, measurements, alpha, M)
        opt[j] = ((np.abs(extm) - alpha) / alpha) * optval
        Ul, Kl, coefficients = _sparsify(Ul, Kl, coefficients)
        Uk = Function(V)
        Uk.vector()[:] = mean.flatten() * U0_arr + Ul @ coefficients
        energy[j] = misfit + alpha * TV(mesh, vol_face_fn, bdy_length_fn, int_lengths, int_cells, bdy_length,
                                        bdy_faces, Uk)
        rel_change[j] = assemble(abs(Uk - prev_Uk) * dx)/assemble(abs(Uk) * dx)

        pp.plot(range(len(np.trim_zeros(opt))), np.log10(np.trim_zeros(opt)))
        pp.xlabel("iteration")
        pp.ylabel("convergence indicator")
        pp.savefig(rd + '/indicator.png', dpi=300)
        pp.close()
        pp.plot(range(len(np.trim_zeros(energy))), np.trim_zeros(energy))
        pp.xlabel("iteration")
        pp.ylabel("energy")
        # pp.ylim([0, 0.001])
        pp.savefig(rd + '/energy.png', dpi=300)
        pp.close()
        flog.write("  Current surrogate energy value is %.6e, with convergence indicator, %.6e \n" % (optval, opt[j]))
        flog.write("  Current actual energy value is %.6e \n" % (energy[j]))
        print("Step %s of GCG finished with energy value %.6e and convergence indicator %.6e \n" % (j, optval, opt[j]))

        plot_result(mesh, int_cells, flog, rd, Uk, 2, j, d)
        plot_result(mesh, int_cells, flog, rd, Pkp, 3, j, d)

        total_time += time.time() - start_iteration_time
        export = [j, total_time, energy[j], opt[j], rel_change[j]]
        data.append(export)
        j += 1

    data_array = np.array(data, dtype=float)
    energy_change = data_array[:, 2] - data_array[-1, 2]
    data_array = np.hstack((data_array, energy_change[:, np.newaxis]))
    fmt = ['%d', '%.2f', '%.6e', '%.6e', '%.6e', '%.6e']
    header = 'iteration, time (seconds), energy, indicator, L1-relative change, energy difference'
    np.savetxt(rd + '/output.csv', data_array, delimiter=',', fmt=fmt, header=header)

    print("Algorithm converged in %s steps, the total time was %.2f seconds" % (j - 1, time.time() - start_time))
    flog.write("The total time was %.2f seconds" % (time.time() - start_time))
    flog.close()

# ----------------------------------------------------------------------------------------------------------------------

if __name__ == '__main__':
    # Step 1: Create an instance of ArgumentParser
    parser = argparse.ArgumentParser()

    # Step 2: Add the --config argument
    dir = os.path.join(os.path.dirname(__file__), 'setup.conf')
    parser.add_argument('--config', type=str, help=dir, default='./setup.conf')

    # Step 3: Parse the command-line arguments
    args = parser.parse_args()

    # Step 4: Read the configuration file
    config = configparser.ConfigParser()
    config.read(args.config)

    # Step 5: Access values from the configuration file
    Random_mesh = eval(config.get('Mesh', 'Random_mesh', fallback='True'))
    Nx, Ny, Nz = eval(config.get('Mesh', 'Size', fallback='[200,200,200]'))
    N = eval(config.get('Mesh', 'N2D', fallback='100'))
    N2 = eval(config.get('Mesh', 'N3D', fallback='50'))
    d = eval(config.get('Mesh', 'dimension', fallback='2'))

    alpha = eval(config.get('Regularizer', 'Parameter', fallback='0.0001'))
    boundary = eval(config.get('Regularizer', 'Boundary', fallback='False'))

    lx1, lx2 = eval(config.get('Control', 'Domain_x', fallback='[-1,1]'))
    ly1, ly2 = eval(config.get('Control', 'Domain_y', fallback='[-1,1]'))
    lz1, lz2 = eval(config.get('Control', 'Domain_z', fallback='[-1,1]'))

    max_iterations = eval(config.get('GCG', 'max_iterations', fallback='120'))
    tolerance = eval(config.get('GCG', 'tolerance', fallback='1e-10'))

    _main_()


from django.urls import URLPattern, path
from . import views

app_name = 'sneakers_app'
urlpatterns = [
    path('', views.index, name='index'),
    path('login/', views.user_login, name='user_login'),
    path('register/', views.register, name='register'),
    path('profile/', views.profile, name='profile'),
    path('logout/', views.user_logout, name='logout'),
    path('favorite/', views.favorite, name='favorite'),
    path('favorite/<int:id>', views.favorite_detail,
         name='favorite_page_detail'),
]


# 레벨2     Summer/Winter Coding(~2018)     방문 길이
# 19분 40초 소요

from collections import defaultdict

def solution(dirs):
    y, x = 0, 0
    cnt = 0
    check = defaultdict(lambda: defaultdict(lambda: 0))
    for i in dirs:
        if i == "U":
            dy, dx = y - 1, x
        elif i == "D":
            dy, dx = y + 1, x
        elif i == "L":
            dy, dx = y, x - 1
        elif i == "R":
            dy, dx = y, x + 1

        if -5 <= dy <= 5 and -5 <= dx <= 5:
            if check[(y, x)][(dy, dx)] == 0:
                cnt += 1
                check[(y, x)][(dy, dx)] = 1
                check[(dy, dx)][(y, x)] = 1
            y, x = dy, dx
    return cnt

if __name__ == "__main__":
    print(solution("UD"))

모든 문제/SummerWinter Coding(~2018)/방문 길이.py


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
        ('my_game', '0004_basicgenerator_fuel'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='fuelpattern',
            name='fuel_id',
        ),
    ]


import random
import math
from random import randint

#GENERACION DE POBLACION
#1.Recibe como entrada el tamaño de la población y genera como salida una lista de cromosomas.
obj_entrada= int(input("Introduce la cantidad de objetos totales: "))
pob_entrada = int(input("Introduce el tamano de la poblacion: "))
objetos= (2**obj_entrada)-1
aptitud_total=0
aptitudes={} #crea diccionario vacío
lista_apt=[]
mejor_cromosoma=''
mejor_aptitud=0
repeticion=5
li_rul = []
lista_rul= []

lista = range(1,objetos)
poblacion = random.sample(lista, k=pob_entrada)
print("\n[GENERACION DE POBLACION]\n")
print(poblacion)

#.append =  agregar en lo ultimo de la lista el elemento()
lista_bin = []
for i in poblacion:
  binario=bin(i)[2:]
  binario=binario.zfill(obj_entrada)
  lista_bin.append(binario)

print("\nLISTA DE BINARIO")
print(lista_bin)

lista= range(1,70)
peso_de_objetos= random.sample(lista, k= obj_entrada) #Se crean los valores del peso de la mochila
lista= range(1,90)
ganancia_de_objetos= random.sample(lista, k= obj_entrada) #Se crean los valores de la ganancia de la mochila
cap_mochi = random.randint(100, 350)  #Se crea la capacidad de la mochila

print("\nCAPACIDAD MOCHILA")
print(cap_mochi)
print("\nPESO DE OBJETOS")
print(peso_de_objetos)
print("\nGANANCIA DE OBJETOS")
print(ganancia_de_objetos)
#EVALUACION
#2-Recibe como entrada una lista de cromosomas y una instancia del problema de la mochila (capacidad, valores y pesos de los objetos); devuelve un diccionario donde cada elemento tiene la forma (cromosoma, aptitud).

for i in range(repeticion):
  aptitud_total=0
  for cromosoma in lista_bin:
      aptitud=0.0
      peso=0.0
      for i in range(0,obj_entrada):
          aptitud+=int(cromosoma[i])*ganancia_de_objetos[i] #Dentro de esta instrucción se encuentra almacenando las aptitudes generadas por la ganancia de los objetos
          peso+=int(cromosoma[i])*peso_de_objetos[i]
      if peso > cap_mochi:
          aptitud=0.0
      if aptitud> mejor_aptitud:
        mejor_aptitud= aptitud
        mejor_cromosoma=cromosoma
      lista_apt.append(aptitud)
      aptitudes[cromosoma]=aptitud #guarda la aptitud teniendo como clave el numero binario
      aptitud_total+=aptitud

  print("_________________________________________\n")
  print("_________________________________________\n")
  print("[EVALUACION GENERACION ACTUAL]\n")
  for i in range(len(lista_apt)):
    print(lista_apt[i], "     ", lista_bin[i])

  print("Aptitud Total: ", aptitud_total, "\n")
  print("\n_________________________________________\n")

  #SELECCIÓN
  #Recibe como entrada un diccionario de elementos con la forma (cromosoma, aptitud) y devuelve el conjunto de parejas a cruzar. Es importante calcular el número de parejas adecuado para reemplazar a la generación actual.

  for i in lista_apt:
    li_rul.append(math.ceil((i/aptitud_total)*100))

  ruleta_unit=0
  for cromosoma in aptitudes:
    ruleta_unit=int(math.ceil((aptitudes[cromosoma]/aptitud_total)*100))
    for i in range(0,ruleta_unit):
      lista_rul.append(cromosoma)
  random.shuffle(lista_rul)

  print("_________________________________________\n")
  print ("[SELECCION]\n")
  for i in range(len(lista_bin)):
    print("Al cromosoma ",lista_bin[i], "le corresponden ", li_rul[i],"espacios")
  print("\n_________________________________________\n")

  #CRUZA
  #Recibe como entrada el conjunto de parejas a cruzar y devuelve la lista de cromosomas hijos.  Recuerda que se debe establecer un punto de cruce. Tip: en Python, considera el slicing en cadenas.

  punto_cruce= randint(1, obj_entrada-1)
  #random.randint(1,obj_entrada-2)
  hijos=[]

  h= int(len(poblacion)/2)
  print("_________________________________________\n")
  print('[CRUCE]')
  print('El punto de cruce escogido es', punto_cruce)

  for  i in range(h):

    madre=random.choice(lista_rul)#Saca un elemento de la ruleta de manera aleatoria para ser mamá y papá
    while True: #verificar si  el padre no es lo mismo que la madre, SINO se             repite
      padre=random.choice(lista_rul)
      if(padre != madre):
        break

    primer_hijo=madre[:punto_cruce] + padre[punto_cruce:] #Combinamos las cadenas usando las cromosomas.
    segundo_hijo=padre[:punto_cruce] + madre[punto_cruce:]
    hijos.append(primer_hijo) #Almacenamos a los hijos.
    hijos.append(segundo_hijo)
    print('Papas ', padre, ' ', madre, ' generan hijos ', primer_hijo, ' y ', segundo_hijo)

  print("_________________________________________\n")

  li_rul.clear()
  lista_rul.clear()
  #MUTACIÓN
  #Recibe como entrada la lista de cromosomas hijos y devuelve estos cromosomas mutados de acuerdo con una probabilidad establecida. Para saber qué genes mutar, se barre cada cromosoma y por cada gen se obtiene un número aleatorio entre 0 y 1. Si este número es menor o igual a la probabilidad de mutación, se muta el gen (0 se cambia por 1 y viceversa)

  prob_mut=random.randint(1,3)/10.0

  lista_bin.clear()

  for cromosoma in hijos:
    crom_mut=''
    for gen in cromosoma:
      resultado=random.randint(1,10)/10.0
      if(resultado <= prob_mut):
        crom_mut+=str(abs(int(gen)-1)) #se cambia
      else:
        crom_mut+=gen
    lista_bin.append(crom_mut)

  print("_________________________________________\n")
  print('[MUTACION]')
  for i in range(len(lista_bin)):
    print('Mutacion ', i+1, ': ', lista_bin[i])
  print("_________________________________________\n")
  print("_________________________________________\n")
  print("[NUEVA GENERACION]\n")
  print(lista_bin) #hijos ya mutados

  hijos.clear()
  lista_apt.clear()

aptitud_total=0
for cromosoma in lista_bin:
  aptitud=0.0
  peso=0.0
  for i in range(0,obj_entrada):
    aptitud+=int(cromosoma[i])*ganancia_de_objetos[i] #Dentro de esta instrucción se encuentra almacenando las aptitudes generadas por la ganancia de los objetos
    peso+=int(cromosoma[i])*peso_de_objetos[i]
  if peso > cap_mochi:
    aptitud=0.0
  if aptitud> mejor_aptitud:
    mejor_aptitud= aptitud
    mejor_cromosoma=cromosoma
  lista_apt.append(aptitud)
  aptitudes[cromosoma]=aptitud #guarda la aptitud teniendo como clave el numero binario
  aptitud_total+=aptitud

print("_________________________________________\n")
print("_________________________________________\n")

print ("[MEJOR APTITUD]: ", mejor_aptitud)
print ("[MEJOR CROMOSOMA]: ", mejor_cromosoma)


# ===========================================================================
# +++hw4.py+++|
# ____________|
#
# Author: Sean Frischmann
# Class: Cse 305
# ===========================================================================
import sys
import evaluater
import closure
import primitives

def print_list(stack):
	for i, v in enumerate(stack):
		print (v)

def load(inputFile):
	buf = ''
	with open(inputFile) as fileIn:
		while True:
			contents = fileIn.read(1)
			if not contents:
				fileIn.close()
				break;
			if contents == '\n':
				if buf[len(buf)-1] != ' ':
					buf = buf + ' '
			else:
				buf = buf + contents
	if buf[len(buf)-1] == ' ':
		buf = buf[0:len(buf)-1]
	return buf

def repl():
	print (
		'\nInterpreter\n'
		'Author: Sean Frischmann\n'
		'Class: Cse 305\n'
	)
	env = dict()
	env['stack'] = list()
	env['global'] = dict()
	env['active'] = list()
	env['global']['bindings'] = dict()
	env['global']['closures'] = list()
	env['global']['binded closures'] = dict()
	env['inactive'] = list()
	mode = 'default'
	buf = ''
	isSpace = False
	isNested = False
	apply_count = 0
	temp_buf = []
	temp_position = []
	active_env = 0
	closure_count = 0
	user_input = ''
	while True:
		position=0
		if apply_count > 0:
			if isNested:
				isNested = False
				env['inactive'] = list()
			if len(temp_buf) != 0:
				buf = temp_buf[0]
				temp_buf.pop(0)
				position = temp_position[0]
				temp_position.pop(0)
				position += 1
			apply_count += -1
			active_env += -1
			if len(env['active']) != 0:
				env['active'].pop(0)
		elif mode == 'default':
			buf = input('repl> ')
		else:
			buf = input(' ')
		while position < len(buf):
			if buf[position] == '"':
				if mode == 'closure':
					mode = 'closure-string'
				elif mode == 'closure-string':
					mode = 'closure'
				elif mode == 'string':
					mode = 'default'
				else:
					mode = 'string'
			if buf[position] == '{':
				closure_count = closure_count + 1
				mode = 'closure'
			if (buf[position] == ' ') and (mode == 'default'):
				isSpace = True
			else:
				user_input = user_input + buf[position]
			if isSpace or position == len(buf)-1:
				if user_input == 'apply':
					topValue = env['stack'][0]
					if topValue[0] == '<':
						topValue = primitives.get_not_local(topValue)
					if topValue == ':closure:':
						temp_buf.insert(0, buf)
						temp_position.insert(0, position)
						closureValue = env['global']['closures'][0]
						env['active'].insert(0,{})
						env['active'][0]['bindings'] = dict()
						env['active'][0]['binded closures'] = dict()
						if isinstance(closureValue, dict):
							isNested = True
							linked_env = []
							for val in closureValue.values():
								linked_env = linked_env + val
							env['inactive'] = linked_env
							env['inactive'].insert(0,env['active'][0])
							for val in closureValue.keys():
								closureValue = val
						buf = closureValue
						env['global']['closures'].pop(0)
						env['stack'].pop(0)
						active_env += 1
					else:
						buf = ':error:' + buf[position+1:len(buf)]
					user_input = ''
					apply_count += 1
					isSpace = False
					position = 0
					continue
				elif user_input == 'load':
					if len(env['stack']) > 0 and (env['stack'][0])[0] == '"':
						try:
							inputFile = env['stack'][0]
							inputFile = inputFile.replace('"','')
							if position == len(buf)-1:
								buf = buf[position+1:len(buf)]
							else:
								buf = buf[position:len(buf)]
							buf = load(inputFile)+' '+':true:'+buf
							env['stack'].pop(0)
							user_input = ''
							position = 0
							continue
						except:
							user_input = ':false:'
							continue
				elif mode == 'string':
					user_input = user_input + '\n'
				elif mode == 'default':
					evaluater.evaluate(user_input,env, active_env, isNested)
					user_input = ''
					isSpace = False
			if buf[position] == '}' and mode == 'closure':
				closure_count = closure_count - 1
				if closure_count == 0:
					isFrontValid = True
					isEndValid = True
					isMore = True
					if user_input[1] != ' ':
						isFrontValid = False
						temp = ':error:'+user_input[2:len(user_input)]
						user_input = temp
					if user_input[len(user_input)-2] != ' ':
						isEndValid = False
						if isFrontValid:
							temp = ':error:'+user_input[1:len(user_input)]
							user_input = temp
					if (position+1) < len(buf):
						if buf[position+1] != ' ':
							if isFrontValid:
								isFrontValid = False
								temp = ':error:'+user_input[1:len(user_input)]
								user_input = temp
						else:
							isMore = True
					if isFrontValid and isEndValid:
						closure.createClosure(user_input, env, active_env)
						if isMore:
							position = position+1
					else:
						buf = user_input + buf[position+1:len(buf)]
						position = -1
					mode = 'default'
					user_input = ''
			position = position+1
		if mode == 'default' and (apply_count == 0):
			print_list(env['stack'])

repl()


# Exercise 1
# Напишете програма която на основата на текст, въведен от потребителя се създава нов текст,
# в който в сравнение с изходния, думите за разположени в обратен ред

text = input('Enter text: ')
new = text.split()
new.reverse()
x = ' '.join(new)
print(x)

# Exercise 2
# Напишете програма, в която се използва речник.
# Ключовете в речника са фамилии на писатели, а стойностите – имената на произведения, написани от тях.
# В програмата се обхождат стойностите на всички елементи в речника и за всяка от тях(име на произведение)
# потребителя въвежда фамилия на автора.След обхождане на съдържанието на речника и получаването на всички отговори,
# програмата показва на потребителя броя на правилните отговори.

books = {
    'Zusak' : 'The Book Thief',
    'Christie' : 'And Then There Were None',
    'Doyle' : 'A Study in Scarlet',
    'Wilde' : 'The Picture of Dorian Gray'
}
count = 0

for i in books.keys():  # тук обхождаме авторите
    print(books[i]) # тук ще ни принтитра името на книгата
    x = input("Enter author's last name: ")
    if x == i:
        count += 1

print(f"Correct: {count}")

# Exercise 3
# Напишете програма, в която на основата на текст, въведен от потребителя, се създава кортеж.
# На основата на този кортеж се създава нов кортеж, в него се включват равноотстоящите елементи, започвайки
# от първия(с нулев индекс). Разстоянието между елементите се въвежда от потребителя.
# Пример:
# Enter string: devetstotin
# ('d', 'e', 'v', 'e', 't', 's', 't', 'o', 't', 'i', 'n')
# Enter distance: 2
# ('d', 'v', 't', 't', 't', 'n')

text = input('Въведи текст: ')
tuple1 = tuple(text)
distance = int(input('Въведи разстояние между елементите: '))
tuple2 = tuple1[::distance]
print(tuple2)

# Exercise 4
# Напишете програма, в която потребителят въвежда две числа, а програмата определя цифрите,
# които са общи в представянията на двете числа.

number1 = input('Enter the first number: ')
number2 = input('Enter the second number: ')
set1 = set(number1)
set2 = set(number2)
set3 = set()

for i in set1:
    for j in set2:
        if i == j:
            set3.add(i)
print(set1)
print(set2)
print(set3)


from flask import Flask

from opencensus.ext.flask.flask_middleware import FlaskMiddleware

app = Flask(__name__)
middleware = FlaskMiddleware(app)

@app.route('/')
def hello():
    return 'Hello World!'

if __name__ == '__main__':
    import logging
    logger = logging.getLogger('werkzeug')
    logger.setLevel(logging.ERROR)
    app.run(host='localhost', port=8080, threaded=True)


from binance.client import Client
import config
client = Client(config.api_key, config.api_secret)
print("logged in")

info = client.get_account()

#for i in info:
 #   print(i)

bal= info['balances']
for b in bal:
    if float(b['free']) > 0:
        print(b)


# A/B Testing of Website Engagement
#dataset location = https://www.kaggle.com/datasets/zhangluyuan/ab-testing?select=ab_data.csv

#Importing Required Libraries
import numpy as np
import pandas as pd
import scipy.stats as stats
import statsmodels.stats.api as sms
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from math import ceil

#Setting plot params
plt.style.use('seaborn-whitegrid')
font = {'family' : 'Arial',
        'weight' : 'bold',
        'size'   : 12}

#Sample size calculation
effect_size = sms.proportion_effectsize(0.13, 0.15)

required_n = sms.NormalIndPower().solve_power(
    effect_size,
    power=0.8,
    alpha=0.05,
    ratio=1
    )

required_n = ceil(required_n)

print(required_n)

#Importing dataset
df = pd.read_csv('ab_data.csv')

#Identifying multiple visit users
session_counts = df['user_id'].value_counts(ascending=False)
multi_users = session_counts[session_counts > 1].count()

#Dropping multiple visit users
users_to_drop = session_counts[session_counts > 1].index
df = df[~df['user_id'].isin(users_to_drop)]

#Seperating groups, obtaining sample size
control_sample = df[df['group'] == 'control'].sample(n=required_n, random_state=22)
treatment_sample = df[df['group'] == 'treatment'].sample(n=required_n, random_state=22)

ab_test = pd.concat([control_sample, treatment_sample], axis=0)
ab_test.reset_index(drop=True, inplace=True)

#Identifying converted users
conversion_rates = ab_test.groupby('group')['converted']

#Generating some basic statistics
std_p = lambda x: np.std(x, ddof=0)
se_p = lambda x: stats.sem(x, ddof=0)
conversion_rates = conversion_rates.agg([np.mean, std_p, se_p])
conversion_rates.columns = ['conversion_rate', 'std_deviation', 'std_error']

#Plotting conversion rates
plt.figure(figsize=(8,6))

sns.barplot(x=ab_test['group'], y=ab_test['converted'], ci=False, color="red")

plt.ylim(0, 0.17)
plt.title('Conversion rate by group', pad=20)
plt.xlabel('Group', labelpad=15)
plt.ylabel('Converted (proportion)', labelpad=15);

#Testing hypothesis
from statsmodels.stats.proportion import proportions_ztest, proportion_confint
control_results = ab_test[ab_test['group'] == 'control']['converted']
treatment_results = ab_test[ab_test['group'] == 'treatment']['converted']
n_con = control_results.count()
n_treat = treatment_results.count()
successes = [control_results.sum(), treatment_results.sum()]
nobs = [n_con, n_treat]
z_stat, pval = proportions_ztest(successes, nobs=nobs)
(lower_con, lower_treat), (upper_con, upper_treat) = proportion_confint(successes, nobs=nobs, alpha=0.05)

#Printing z-stat, p-value, and ci's
print(f'z statistic: {z_stat:.2f}')
print(f'p-value: {pval:.3f}')
print(f'ci 95% for control group: [{lower_con:.3f}, {upper_con:.3f}]')
print(f'ci 95% for treatment group: [{lower_treat:.3f}, {upper_treat:.3f}]')


from abc import ABC

class Element(ABC):
    """
    Class represents the general element to be added into the <body> tag object.
    This is an abstract base class intended only for inheritance purposes.
    No instances of this class should ever be created.
    """

    def __init__(self, id="", classname=""):
        self._id = id
        self._classname = classname
        self._children = []

    def _unpack_children(self):
        """
        Unpacks the children elements contained in the self._children list.
        """
        self._unpacked_children = "\n".join(str(_) for _ in self._children)

    def set_attribute(self, attribute, value):
        """
        Sets the element's attribute to given value.
        :param str attribute: Either 'id' or 'class'.
        :param str value: Value to be set as attribute.
        :raises ValueError: Enter either 'id' or 'class' as parameter.
        """
        try:
            if attribute == "id":
                self._id = value
            elif attribute == "class":
                self._classname = value
        except:
            raise ValueError("Enter either 'id' or 'class' as parameter.")

    def get_attribute(self, attribute):
        """
        Returns the element's attribute based on attribute specified in the parameter.
        :param str attribute: Either 'id' or 'class'.
        :raises ValueError: Enter either 'id' or 'class' as parameter.
        """
        try:
            if attribute == "id":
                return self._id
            elif attribute == "class":
                return self._classname
        except:
            raise ValueError("Enter either 'id' or 'class' as parameter.")

    def add_child(self, child):
        """
        Checks whether child to be added is an instance of Element and appends it to the list of children if true.
        :param Element child: Another tag object to be added as a child. Must be of Element object type.
        :raises TypeError: Child must be an instance of Element.
        """
        try:
            if isinstance(child, Element):
                self._children.append(child)
        except:
            raise TypeError("Child must be an instance of Element.")

    def remove_child(self, child):
        """
        Checks whether child to be removed is contained within the ancestor's children list and removes it if true.
        :param Element child: Another tag object to be removed as a child.
        :raises ValueError: Child was not found.
        """
        try:
            if child in self._children:
                self._children.remove(child)
        except:
            raise ValueError("Child was not found.")


from copy import copy, deepcopy

import numpy as np
from Basilisk.simulation import vizInterface
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros as mc
from Basilisk.utilities import orbitalMotion, vizSupport
from scipy.sparse.csgraph import connected_components

from bsk_rl.envs.multisat_agile_eos.bsk_models import dynamics, environment
from bsk_rl.envs.multisat_agile_eos.bsk_models import fsw as fsw_feedback
from bsk_rl.envs.multisat_agile_eos.bsk_models import fsw_steering
from bsk_rl.utilities.initial_conditions import leo_initial_conditions

class MultiSatAgileEOSSimulator(SimulationBaseClass.SimBaseClass):
    def __init__(
        self,
        envRate,
        dynRate,
        fswRate,
        step_duration,
        initial_conditions=None,
        render=False,
        n_targets=1,
        max_length=270.0,
        target_tuple_size=4,
        n_spacecraft=2,
        n_planes=2,
        rel_phasing=0,
        inc=45,
        global_tgts=None,
        priorities=None,
        comm_method="free",
        clustersize=1,
        clusterspacing=0,
        control_method="feedback",
        renew_tasks=True,
        target_indexing="local",
    ):
        """
        Simulates multiple spacecraft in LEO with atmospheric drag and J2.

        Dynamics Components
        - Forces: J2, Atmospheric Drag
        - Environment: Exponential density model; eclipse
        - Actuators: ExternalForceTorque, reaction wheels
        - Sensors: SimpleNav
        - Power System: SimpleBattery, SimplePOwerSink, SimpleSolarPanel
        - Data Management System: spaceToGroundTransmitter, simpleStorageUnit,
        simpleInstrument

        FSW Components:
        - MRP Feedback controller
        - locationPoint - targets, sun-pointing
        - Desat
        """
        # Initialize the SimBase object
        SimulationBaseClass.SimBaseClass.__init__(self)

        # Simulation settings
        assert target_indexing in [
            "local",
            "global",
        ], f"target_indexing {target_indexing} not valid!"
        self.target_indexing = (
            target_indexing  # Whether local or global target (action-3) is targeted
        )
        assert comm_method in [
            "free",
            "los",
            "los-multi",
            "none",
        ], f"comm_method {comm_method} not valid!"
        self.comm_method = comm_method
        assert control_method in [
            "fb",
            "feedback",
            "steer",
            "steering",
        ], f"control_method {control_method} not valid!"
        self.control_method = control_method
        self.renew_tasks = (
            renew_tasks  # If false, do not reinitialize task if same task selected
        )
        self.envRate = envRate
        self.dynRate = dynRate
        self.fswRate = fswRate
        self.render = render

        # Simulation properties
        self.initialized = False
        self.step_duration = step_duration
        self.attRefMsg = None
        self.attGuidMsg = None
        self.currentResetTime = 0.0
        self.prev_action = [0.0] * n_spacecraft
        self.prev_target = [-1] * n_spacecraft
        self.curr_step = 0
        self.max_length = max_length  # minutes
        self.nominal_radius = 6371 * 1000.0 + 500.0 * 1000

        # Initialize performance metrics
        self.total_downlinked = [0.0] * n_spacecraft
        self.total_access = [0.0] * n_spacecraft
        self.utilized_access = [0.0] * n_spacecraft

        # Set the constellation and environment parameters
        self.n_spacecraft = n_spacecraft
        self.n_planes = n_planes  # Put one spacecraft in each plane
        self.rel_phasing = rel_phasing  # deg
        self.inc = inc  # deg
        self.clusterspacing = clusterspacing
        self.clustersize = clustersize
        if priorities is None:
            priorities = []
        self.priorities = priorities
        if global_tgts is None:
            global_tgts = []
        self.global_tgts = global_tgts

        # Set the number of targets in the simulator
        self.n_targets = n_targets  # Number of targets in the action space for each s/c
        self.n_target_global = len(global_tgts)  # Maximum number of targets
        self.imaged_targets = {
            sc_idx: [] for sc_idx in range(self.n_spacecraft)
        }  # Per-spacecraft imaged targets, stored by index into global targets
        self.known_imaged_targets = {
            sc_idx: [] for sc_idx in range(self.n_spacecraft)
        }  # Per-spacecraft known imaged targets, stored by index into global targets
        self.downlinked_targets = {
            sc_idx: [] for sc_idx in range(self.n_spacecraft)
        }  # Per-spacecraft downlinked targets, stored by index into global targets
        self.known_downlinked_targets = {
            sc_idx: [] for sc_idx in range(self.n_spacecraft)
        }  # Per-spacecraft known downlinked targets, stored by idx into global targets
        self.global_target_log = {
            target_idx: [] for target_idx in range(len(self.global_tgts))
        }
        self.current_tgt_indices = [list(np.arange(self.n_targets))] * self.n_spacecraft

        # Set the communication method
        self.set_up_comms()

        # Set the size of the observations
        self.target_tuple_size = target_tuple_size
        self.obs = np.zeros(
            [self.n_spacecraft, 22 + self.n_targets * self.target_tuple_size]
        )
        self.obs_full = np.zeros(
            [self.n_spacecraft, 22 + self.n_targets * self.target_tuple_size]
        )
        self.sim_over = False

        # Load ICs
        if (
            initial_conditions is None
        ):  # If no initial conditions are defined yet, set ICs
            self.initial_conditions = self.set_ICs()
        else:  # If ICs were passed through, use the ones that were passed through
            self.initial_conditions = initial_conditions

        #################################################
        # Initialize dynModels, FSWModels, processes here
        #################################################

        # Connect the environment, dynamics and fsw scripts
        self.set_environment(environment)
        self.set_dynamics([dynamics] * n_spacecraft)
        if self.control_method in ["fb", "feedback"]:
            self.set_fsw([fsw_feedback] * n_spacecraft)
        elif self.control_method in ["steer", "steering"]:
            self.set_fsw([fsw_steering] * n_spacecraft)

        if self.render:
            self.vizInterface = None
            self.setup_viz()
            self.clear_logs = False
            self.vizSupport = vizSupport
        else:
            self.clear_logs = True

        self.set_logging()
        self.init_obs()

        self.modeRequests = None
        self.InitializeSimulation()
        self.initialized = True

        return

    def __del__(self):
        self.close_gracefully()
        return

    @property
    def simTime(self):
        """Simulation time in seconds, tied to SimBase integrator."""
        return self.TotalSim.CurrentNanos * mc.NANO2SEC

    def set_ICs(self):
        """
        Sets initial conditions
        :return initial_conditions: dictionary of spacecraft ICs, key is spacecraft num
        """
        initial_conditions = leo_initial_conditions.walker_delta_n_spacecraft_500_km(
            self.n_spacecraft,
            self.n_planes,
            self.rel_phasing,
            self.inc,
            self.global_tgts,
            self.priorities,
            self.max_length,
            clustersize=self.clustersize,
            clusterspacing=self.clusterspacing,
        )

        return initial_conditions

    def set_environment(self, envModel):
        """
        Sets up the environment modules for the sim.
        :return:
        """
        self.EnvProcessName = "EnvironmentProcess"
        self.envProc = self.CreateNewProcess(self.EnvProcessName, 300)

        # Add the environment class
        self.EnvModel = envModel.EnvironmentModel(self, self.envRate)

    def set_dynamics(self, dynModel):
        """
        Sets up the dynamics modules for the sim.
        :return:
        """
        self.DynamicsProcessName = []
        self.dynProc = []
        self.DynModels = []
        # Add the dynamics classes
        for spacecraftIndex in range(self.n_spacecraft):
            self.DynamicsProcessName.append(
                "DynamicsProcess" + str(spacecraftIndex)
            )  # Create simulation process name
            self.dynProc.append(
                self.CreateNewProcess(self.DynamicsProcessName[spacecraftIndex], 200)
            )  # Create process
            self.DynModels.append(
                dynModel[spacecraftIndex].DynamicModel(
                    self,
                    self.dynRate,
                    spacecraftIndex,
                    singleSat=(self.n_spacecraft == 1),
                )
            )
        for model in self.DynModels:
            model.ConnectLosComms([addmodel.scObject for addmodel in self.DynModels])

    def set_fsw(self, fswModel):
        """
        Sets up the fsw modules for the sim.
        :return:
        """
        self.FSWProcessName = []
        self.fswProc = []
        self.FSWModels = []
        # Add the FSW classes
        for spacecraftIndex in range(self.n_spacecraft):
            self.FSWProcessName.append(
                "FSWProcess" + str(spacecraftIndex)
            )  # Create simulation process name
            self.fswProc.append(
                self.CreateNewProcess(self.FSWProcessName[spacecraftIndex], 100)
            )  # Create process
            self.FSWModels.append(
                fswModel[spacecraftIndex].FSWModel(self, self.fswRate, spacecraftIndex)
            )

    def init_obs(self):
        """
        Initializes the observations for each spacecraft
        """
        for sc_idx in range(self.n_spacecraft):
            # Initialize the observations (normed)
            # Inertial position
            self.obs[sc_idx, 0] = (
                self.DynModels[sc_idx].scObject.hub.r_CN_NInit[0][0]
                / self.initial_conditions["env_params"]["planetRadius"]
            )
            self.obs[sc_idx, 1] = (
                self.DynModels[sc_idx].scObject.hub.r_CN_NInit[1][0]
                / self.initial_conditions["env_params"]["planetRadius"]
            )
            self.obs[sc_idx, 2] = (
                self.DynModels[sc_idx].scObject.hub.r_CN_NInit[2][0]
                / self.initial_conditions["env_params"]["planetRadius"]
            )
            # Inertial velocity
            self.obs[sc_idx, 3] = self.DynModels[sc_idx].scObject.hub.v_CN_NInit[
                0
            ] / np.linalg.norm(self.DynModels[sc_idx].scObject.hub.v_CN_NInit)
            self.obs[sc_idx, 4] = self.DynModels[sc_idx].scObject.hub.v_CN_NInit[
                1
            ] / np.linalg.norm(self.DynModels[sc_idx].scObject.hub.v_CN_NInit)
            self.obs[sc_idx, 5] = self.DynModels[sc_idx].scObject.hub.v_CN_NInit[
                2
            ] / np.linalg.norm(self.DynModels[sc_idx].scObject.hub.v_CN_NInit)
            # Attitude error
            self.obs[sc_idx, 6] = np.linalg.norm(
                self.DynModels[sc_idx].scObject.hub.sigma_BNInit
            )
            # Attitude rate
            self.obs[sc_idx, 7] = np.linalg.norm(
                self.DynModels[sc_idx].scObject.hub.omega_BN_BInit
            )
            # Wheel speeds
            self.obs[sc_idx, 8] = self.initial_conditions[str(sc_idx)]["wheelSpeeds"][
                0
            ] / (mc.RPM * self.initial_conditions[str(sc_idx)]["maxSpeed"])
            self.obs[sc_idx, 9] = self.initial_conditions[str(sc_idx)]["wheelSpeeds"][
                1
            ] / (mc.RPM * self.initial_conditions[str(sc_idx)]["maxSpeed"])
            self.obs[sc_idx, 10] = self.initial_conditions[str(sc_idx)]["wheelSpeeds"][
                2
            ] / (mc.RPM * self.initial_conditions[str(sc_idx)]["maxSpeed"])
            # Stored charge
            self.obs[sc_idx, 11] = (
                self.DynModels[sc_idx].powerMonitor.storedCharge_Init
                / self.initial_conditions[str(sc_idx)]["batteryStorageCapacity"]
            )

            # Initialize the full observations
            # Inertial position
            self.obs_full[sc_idx, 0:3] = np.asarray(
                self.DynModels[sc_idx].scObject.hub.r_CN_NInit
            ).flatten()
            # Inertial velocity
            self.obs_full[sc_idx, 3:6] = np.asarray(
                self.DynModels[sc_idx].scObject.hub.v_CN_NInit
            ).flatten()
            # Attitude error
            self.obs_full[sc_idx, 6] = np.linalg.norm(
                self.DynModels[sc_idx].scObject.hub.sigma_BNInit
            )
            # Attitude rate
            self.obs_full[sc_idx, 7] = np.linalg.norm(
                self.DynModels[sc_idx].scObject.hub.omega_BN_BInit
            )
            # Wheel speeds
            self.obs_full[sc_idx, 8:11] = (
                self.initial_conditions[str(sc_idx)]["wheelSpeeds"][0:3] * mc.RPM
            )
            # Stored charge
            self.obs_full[sc_idx, 11] = (
                self.DynModels[sc_idx].powerMonitor.storedCharge_Init / 3600.0
            )

            # Eclipse indicator
            self.obs[sc_idx, 12] = self.obs_full[sc_idx, 12] = 0
            # Stored data
            self.obs[sc_idx, 13] = self.obs_full[sc_idx, 13] = 0
            # Transmitted data
            self.obs[sc_idx, 14] = self.obs_full[sc_idx, 14] = 0
            # Ground Station access indicators
            self.obs[sc_idx, 15:22] = self.obs_full[sc_idx, 15:22] = 0

            # Compute the image tuples, add to observations
            image_tuples, image_tuples_norm = self.compute_image_tuples(
                self.obs_full[sc_idx, 0:3], self.obs_full[sc_idx, 3:6], sc_idx
            )
            self.obs[sc_idx, 22:] = image_tuples_norm[
                0 : self.n_targets * self.target_tuple_size
            ]
            self.obs_full[sc_idx, 22:] = image_tuples[
                0 : self.n_targets * self.target_tuple_size
            ]

            self.obs = np.around(self.obs, decimals=5)

    def setup_viz(self):
        """
        Initializes a vizSupport instance
        """
        from datetime import datetime

        fileName = f"multi_tgt_env-v1_{datetime.today()}"  # noqa: F841; unsure if used

        # Add the transceivers
        self.transceiverList = [[] for _ in range(self.n_spacecraft)]
        for idx in range(self.n_spacecraft):
            transceiverHUD = vizInterface.Transceiver()
            transceiverHUD.r_SB_B = [0.0, 0.0, 1.0]
            transceiverHUD.fieldOfView = 40.0 * mc.D2R
            transceiverHUD.normalVector = [-1.0, 0.0, 0.0]
            transceiverHUD.color = vizInterface.IntVector(
                vizSupport.toRGBA255("yellow", alpha=0.5)
            )
            transceiverHUD.label = "antenna"
            self.transceiverList[idx].append(transceiverHUD)

        # Add the sensors
        self.sensorList = [[] for _ in range(self.n_spacecraft)]
        for idx in range(self.n_spacecraft):
            genericSensor = vizInterface.GenericSensor()
            genericSensor.r_SB_B = [1.0, 1.0, 1.0]
            genericSensor.fieldOfView.push_back(20.0 * mc.D2R)
            genericSensor.normalVector = self.FSWModels[idx].locPointConfig.pHat_B
            genericSensor.color = vizInterface.IntVector(vizSupport.toRGBA255("red"))
            genericSensor.label = "genSen" + str(idx)
            self.sensorList[idx].append(genericSensor)

        # Define the spacecraft list
        spacecraftList = [dyn.scObject for dyn in self.DynModels]
        rwList = [dyn.rwStateEffector for dyn in self.DynModels]
        thrList = [[dyn.thrusterSet] for dyn in self.DynModels]

        self.vizInterface = vizSupport.enableUnityVisualization(
            self,
            self.DynModels[0].taskName,
            spacecraftList,
            rwEffectorList=rwList,
            thrEffectorList=thrList,
            genericSensorList=self.sensorList,
            transceiverList=self.transceiverList,
            saveFile="test",
        )

        vizSupport.addLocation(
            self.vizInterface,
            stationName="Boulder Station",
            parentBodyName=self.EnvModel.planet.planetName,
            r_GP_P=self.EnvModel.boulderGroundStation.r_LP_P_Init,
            fieldOfView=np.radians(160.0),
            color="pink",
            range=1000.0 * 1000,  # meters
        )

        vizSupport.addLocation(
            self.vizInterface,
            stationName="Merritt Station",
            parentBodyName=self.EnvModel.planet.planetName,
            r_GP_P=self.EnvModel.merrittGroundStation.r_LP_P_Init,
            fieldOfView=np.radians(160.0),
            color="pink",
            range=1000.0 * 1000,  # meters
        )

        vizSupport.addLocation(
            self.vizInterface,
            stationName="Singapore Station",
            parentBodyName=self.EnvModel.planet.planetName,
            r_GP_P=self.EnvModel.singaporeGroundStation.r_LP_P_Init,
            fieldOfView=np.radians(160.0),
            color="pink",
            range=1000.0 * 1000,  # meters
        )

        vizSupport.addLocation(
            self.vizInterface,
            stationName="Weilheim Station",
            parentBodyName=self.EnvModel.planet.planetName,
            r_GP_P=self.EnvModel.weilheimGroundStation.r_LP_P_Init,
            fieldOfView=np.radians(160.0),
            color="pink",
            range=1000.0 * 1000,  # meters
        )

        vizSupport.addLocation(
            self.vizInterface,
            stationName="Santiago Station",
            parentBodyName=self.EnvModel.planet.planetName,
            r_GP_P=self.EnvModel.santiagoGroundStation.r_LP_P_Init,
            fieldOfView=np.radians(160.0),
            color="pink",
            range=1000.0 * 1000,  # meters
        )

        vizSupport.addLocation(
            self.vizInterface,
            stationName="Dongara Station",
            parentBodyName=self.EnvModel.planet.planetName,
            r_GP_P=self.EnvModel.dongaraGroundStation.r_LP_P_Init,
            fieldOfView=np.radians(160.0),
            color="pink",
            range=1000.0 * 1000,  # meters
        )

        vizSupport.addLocation(
            self.vizInterface,
            stationName="Hawaii Station",
            parentBodyName=self.EnvModel.planet.planetName,
            r_GP_P=self.EnvModel.hawaiiGroundStation.r_LP_P_Init,
            fieldOfView=np.radians(160.0),
            color="pink",
            range=1000.0 * 1000,  # meters
        )

        stationID = 0
        for spacecraftIndex in range(self.n_spacecraft):
            targets = self.initial_conditions[str(spacecraftIndex)]["targetPositions"]
            if spacecraftIndex > 1:
                break
            for idx in range(
                self.initial_conditions[str(spacecraftIndex)]["transmitterNumBuffers"]
            ):
                vizSupport.addLocation(
                    self.vizInterface,
                    stationName=str(spacecraftIndex) + "_" + str(stationID),
                    parentBodyName=self.EnvModel.planet.planetName,
                    r_GP_P=targets[:][idx].tolist(),
                    fieldOfView=np.radians(160.0),
                    color="green",
                    range=1000.0 * 1000,  # meters
                )
                stationID = stationID + 1
                if idx > 10:
                    break

        vizSupport.createTargetLine(
            self.vizInterface, toBodyName=str(0), lineColor="blue"
        )

        self.vizInterface.settings.spacecraftSizeMultiplier = 1.5
        self.vizInterface.settings.showLocationCommLines = -1
        self.vizInterface.settings.showLocationCones = -1
        self.vizInterface.settings.showLocationLabels = 1

    def set_logging(self):
        """
        Sets up BSK logging functionality for the:
        - ground stations
        - transmitter
        - storage unit
        - simpleInstrumentControl access
        - sc Log
        - planetLog
        - LOS comms
        """

        self.boulderGSLogs = []
        self.singaporeGSLogs = []
        self.merrittGSLogs = []
        self.weilheimGSLogs = []
        self.santiagoGSLogs = []
        self.dongaraGSLogs = []
        self.hawaiiGSLogs = []
        self.transmitterLogs = []
        self.storageUnitLogs = []
        self.accessLogs = []
        self.scLogs = []
        self.losLogs = []

        for idx in range(self.n_spacecraft):
            # Add the ground station logs
            self.boulderGSLogs.append(
                self.EnvModel.boulderGroundStation.accessOutMsgs[idx].recorder()
            )
            self.singaporeGSLogs.append(
                self.EnvModel.singaporeGroundStation.accessOutMsgs[idx].recorder()
            )
            self.merrittGSLogs.append(
                self.EnvModel.merrittGroundStation.accessOutMsgs[idx].recorder()
            )
            self.weilheimGSLogs.append(
                self.EnvModel.weilheimGroundStation.accessOutMsgs[idx].recorder()
            )
            self.santiagoGSLogs.append(
                self.EnvModel.santiagoGroundStation.accessOutMsgs[idx].recorder()
            )
            self.dongaraGSLogs.append(
                self.EnvModel.dongaraGroundStation.accessOutMsgs[idx].recorder()
            )
            self.hawaiiGSLogs.append(
                self.EnvModel.hawaiiGroundStation.accessOutMsgs[idx].recorder()
            )

            # Add the other logs
            self.transmitterLogs.append(
                self.DynModels[idx].transmitter.nodeDataOutMsg.recorder()
            )
            self.storageUnitLogs.append(
                self.DynModels[idx].storageUnit.storageUnitDataOutMsg.recorder()
            )
            self.accessLogs.append(
                self.FSWModels[idx].simpleInsControlConfig.deviceCmdOutMsg.recorder()
            )
            self.scLogs.append(self.DynModels[idx].scObject.scStateOutMsg.recorder())

            # Add LOS logs
            losLog = []
            for ilog in range(self.n_spacecraft):
                if ilog < idx:
                    losLog.append(
                        self.DynModels[idx].losComms.accessOutMsgs[ilog].recorder()
                    )
                elif ilog > idx:
                    losLog.append(
                        self.DynModels[idx].losComms.accessOutMsgs[ilog - 1].recorder()
                    )
                else:
                    losLog.append(None)
            self.losLogs.append(losLog)

            # Add the ground station logs to the tasks
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.boulderGSLogs[idx], ModelPriority=599
            )
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.singaporeGSLogs[idx], ModelPriority=598
            )
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.merrittGSLogs[idx], ModelPriority=597
            )
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.weilheimGSLogs[idx], ModelPriority=596
            )
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.santiagoGSLogs[idx], ModelPriority=595
            )
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.dongaraGSLogs[idx], ModelPriority=594
            )
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.hawaiiGSLogs[idx], ModelPriority=593
            )

            # Add the other logs to the tasks
            self.AddModelToTask(
                self.DynModels[idx].taskName,
                self.transmitterLogs[idx],
                ModelPriority=592,
            )
            self.AddModelToTask(
                self.DynModels[idx].taskName,
                self.storageUnitLogs[idx],
                ModelPriority=591,
            )
            self.AddModelToTask(
                self.EnvModel.envTaskName, self.accessLogs[idx], ModelPriority=591
            )
            self.AddModelToTask(
                self.DynModels[idx].taskName, self.scLogs[idx], ModelPriority=587
            )

            # Add LOS logs to the tasks
            for logger in self.losLogs[idx]:
                if logger:
                    self.AddModelToTask(
                        self.DynModels[idx].taskName, logger, ModelPriority=586
                    )

        self.planetLog = self.EnvModel.gravFactory.spiceObject.planetStateOutMsgs[
            self.EnvModel.earth
        ].recorder()
        self.AddModelToTask(
            self.EnvModel.envTaskName, self.planetLog, ModelPriority=587
        )

    def run_sim(self, action, return_obs=True):
        """
        Executes the sim for a specified duration given a mode command.
        :param action: list of actions, index by s/c num
        :return observations: n_spacecraft x obs_size nparray of observations
        """
        # Set the sim_over param to false
        self.sim_over = False
        self.currentResetTime = mc.sec2nano(self.simTime)

        if self.render:
            vizSupport.targetLineList.clear()

        # Loop through each spacecraft and set the modeRequests
        for idx in range(self.n_spacecraft):
            self.FSWModels[idx].modeRequest = str(int(action[idx]))
            if action[idx] >= 3:
                if self.target_indexing == "local":
                    self.EnvModel.imagingTargetList[
                        idx
                    ].r_LP_P_Init = self.initial_conditions[str(idx)].get(
                        "targetPositions"
                    )[
                        :
                    ][
                        self.current_tgt_indices[idx][
                            int(self.FSWModels[idx].modeRequest) - 3
                        ]
                    ]
                elif self.target_indexing == "global":
                    self.EnvModel.imagingTargetList[idx].r_LP_P_Init = self.global_tgts[
                        int(action[idx] - 3), 0:3
                    ]
            if self.render:
                if action[idx] >= 3:
                    vizSupport.createTargetLine(
                        self.vizInterface,
                        fromBodyName="sat-" + str(idx),
                        toBodyName=str(idx)
                        + "_"
                        + str(int(self.current_tgt_indices[idx][int(action[idx]) - 3])),
                        lineColor="blue",
                    )
        if self.render:
            vizSupport.updateTargetLineList(self.vizInterface)

        for idx in range(self.n_spacecraft):
            # Charging
            if int(action[idx]) == 0 and (
                self.renew_tasks or self.prev_action[idx] != 0
            ):
                self.fswProc[idx].disableAllTasks()
                self.FSWModels[idx].zeroGateWayMsgs()
                self.FSWModels[idx].sunPointWrap.Reset(self.currentResetTime)
                self.FSWModels[idx].trackingErrorWrap.Reset(self.currentResetTime)
                self.EnvModel.imagingTargetList[idx].Reset(self.currentResetTime)
                self.FSWModels[idx].locPointWrap.Reset(self.currentResetTime)
                self.FSWModels[idx].simpleInsControlConfig.controllerStatus = 0
                self.DynModels[idx].transmitter.dataStatus = 0
                self.DynModels[idx].transmitterPowerSink.powerStatus = 0
                self.DynModels[idx].instrumentPowerSink.powerStatus = 0
                if self.render:
                    self.transceiverList[idx][0].transceiverState = 0
                if self.render:
                    self.sensorList[idx][0].genericSensorCmd = 0
                self.enableTask("sunPointTask" + str(idx))
                self.enableTask("mrpControlTask" + str(idx))
                self.enableTask("trackingErrTask" + str(idx))
                self.disableTask("locPointTask" + str(idx))

            # Desaturation mode
            elif int(action[idx]) == 1 and (
                self.renew_tasks or self.prev_action[idx] != 1
            ):
                self.fswProc[idx].disableAllTasks()
                self.FSWModels[idx].zeroGateWayMsgs()
                self.FSWModels[idx].sunPointWrap.Reset(self.currentResetTime)
                self.FSWModels[idx].trackingErrorWrap.Reset(self.currentResetTime)
                self.EnvModel.imagingTargetList[idx].Reset(self.currentResetTime)
                self.FSWModels[idx].locPointWrap.Reset(self.currentResetTime)
                self.FSWModels[idx].simpleInsControlConfig.controllerStatus = 0
                self.FSWModels[idx].thrDesatControlWrap.Reset(self.currentResetTime)
                self.FSWModels[idx].thrDumpWrap.Reset(self.currentResetTime)
                self.DynModels[idx].transmitter.dataStatus = 0
                self.DynModels[idx].transmitterPowerSink.powerStatus = 0
                self.DynModels[idx].instrumentPowerSink.powerStatus = 0
                if self.render:
                    self.transceiverList[idx][0].transceiverState = 0
                if self.render:
                    self.sensorList[idx][0].genericSensorCmd = 0
                self.enableTask("sunPointTask" + str(idx))
                self.enableTask("mrpControlTask" + str(idx))
                self.enableTask("rwDesatTask" + str(idx))
                self.enableTask("trackingErrTask" + str(idx))
                self.disableTask("locPointTask" + str(idx))

            # Downlink mode
            elif int(action[idx]) == 2 and (
                self.renew_tasks or self.prev_action[idx] != 2
            ):
                self.fswProc[idx].disableAllTasks()
                self.FSWModels[idx].zeroGateWayMsgs()
                self.FSWModels[idx].hillPointWrap.Reset(self.currentResetTime)
                self.FSWModels[idx].trackingErrorWrap.Reset(self.currentResetTime)
                self.EnvModel.imagingTargetList[idx].Reset(self.currentResetTime)
                self.FSWModels[idx].locPointWrap.Reset(self.currentResetTime)
                self.FSWModels[idx].simpleInsControlConfig.controllerStatus = 0
                self.DynModels[idx].instrumentPowerSink.powerStatus = 0
                self.DynModels[idx].transmitter.dataStatus = 1
                self.DynModels[idx].transmitterPowerSink.powerStatus = 1
                if self.render:
                    self.transceiverList[idx][0].transceiverState = 1
                if self.render:
                    self.sensorList[idx][0].genericSensorCmd = 0
                self.enableTask("nadirPointTask" + str(idx))
                self.enableTask("mrpControlTask" + str(idx))
                self.enableTask("trackingErrTask" + str(idx))
                self.disableTask("locPointTask" + str(idx))

            # Imaging mode
            elif int(action[idx]) >= 3:
                global_tgt = (
                    self.initial_conditions[str(idx)]["targetIndices"][
                        int(self.current_tgt_indices[idx][int(action[idx]) - 3])
                    ]
                    if self.target_indexing == "local"
                    else int(action[idx] - 3)
                    if self.target_indexing == "global"
                    else -1
                )

                if self.renew_tasks or self.prev_target[idx] != global_tgt:
                    self.fswProc[idx].disableAllTasks()
                    self.FSWModels[idx].zeroGateWayMsgs()
                    self.FSWModels[idx].locPointWrap.Reset(self.currentResetTime)
                    self.FSWModels[idx].simpleInsControlConfig.controllerStatus = 1
                    self.EnvModel.imagingTargetList[idx].Reset(self.currentResetTime)
                    self.DynModels[idx].transmitter.dataStatus = 0
                    self.DynModels[idx].transmitterPowerSink.powerStatus = 0
                    self.DynModels[idx].instrumentPowerSink.powerStatus = 1
                    self.EnvModel.imagingTargetList[idx].r_LP_P_Init = self.global_tgts[
                        global_tgt, 0:3
                    ]
                    self.DynModels[idx].instrument.nodeDataName = str(
                        self.initial_conditions[str(idx)]["targetIndices"].index(
                            global_tgt
                        )
                    )
                    self.FSWModels[idx].simpleInsControlConfig.imaged = 0
                    if self.render:
                        self.transceiverList[idx][0].transceiverState = 0
                    if self.render:
                        self.sensorList[idx][0].genericSensorCmd = 1
                    self.enableTask("mrpControlTask" + str(idx))
                    self.enableTask("locPointTask" + str(idx))
                    self.prev_target[idx] = global_tgt

            if int(action[idx]) < 3:
                self.prev_target[idx] = -1

        # Configure the stop time
        simulation_time = mc.sec2nano(self.simTime + self.step_duration)
        self.curr_step += 1

        # Execute the simulation
        self.ConfigureStopTime(simulation_time)
        # self.ShowExecutionOrder()
        self.ExecuteSimulation()

        # Check what has been imaged and communicate
        downlinked_all, imaged_all = self.update_imaged()

        # Observe the system
        obs, sim_over, obs_full = self.get_obs()

        self.prev_action = action

        return obs, sim_over, obs_full, downlinked_all, imaged_all

    def update_imaged(self):
        """
        Update the imaging state of all satellites by adding to individual lists then
        communicating
        :return downlinked: n_spacecraft x n_targets np.array of downlinked targets
        :return imaged: n_spacecraft x n_targets np.array of imaged targets
        """
        # Initialize lists for each sc's imaging and downlink
        imaged_all = []
        downlinked_all = []
        self.current_tgt_indices = []

        # Update imaging state
        for idx in range(self.n_spacecraft):
            downlinked, imaged = self.update_imaged_targets(idx)

            # Append to imaged_all and downlinked_all
            imaged_all.append(imaged)
            downlinked_all.append(downlinked)

        # update satellite beliefs
        self.communicate()

        return downlinked_all, imaged_all

    def get_obs(self):
        """
        Pulls all of the message logs or reads messages for each spacecraft
        :return self.obs: n_spacecraft x obs_size nparray of normalized observations
        :return self.sim_over: T/F if sim is over
        :return self.obs_full: n_spacecraft x obs_size nparray of normalized
        observations
        """
        # Initialize the local obs_full variable
        obs_full = np.zeros(
            [self.n_spacecraft, 22 + self.n_targets * self.target_tuple_size]
        )
        obs_norm = np.zeros(
            [self.n_spacecraft, 22 + self.n_targets * self.target_tuple_size]
        )

        # create observation
        for idx in range(self.n_spacecraft):
            # Compute the relevant state variables
            attErr = self.FSWModels[idx].attGuidMsg.read().sigma_BR
            attRate = self.DynModels[idx].simpleNavObject.attOutMsg.read().omega_BN_B
            storedCharge = (
                self.DynModels[idx].powerMonitor.batPowerOutMsg.read().storageLevel
            )
            storageLevel = (
                self.DynModels[idx]
                .storageUnit.storageUnitDataOutMsg.read()
                .storageLevel
            )
            eclipseIndicator = (
                self.EnvModel.eclipseObject.eclipseOutMsgs[0].read().shadowFactor
            )
            wheelSpeeds = (
                self.DynModels[idx].rwStateEffector.rwSpeedOutMsg.read().wheelSpeeds
            )

            # Get the rotation matrix from the inertial to planet frame from SPICE
            dcm_PN = np.array(
                self.EnvModel.gravFactory.spiceObject.planetStateOutMsgs[
                    self.EnvModel.earth
                ]
                .read()
                .J20002Pfix
            ).reshape((3, 3))

            # Get inertial position and velocity, rotate to planet-fixed frame
            inertialPos = self.DynModels[idx].scObject.scStateOutMsg.read().r_BN_N
            inertialVel = self.DynModels[idx].scObject.scStateOutMsg.read().v_BN_N
            planetFixedPos = np.matmul(dcm_PN, inertialPos)
            planetFixedVel = np.matmul(dcm_PN, inertialVel)  # Should fix - T.T.
            # print('Spacecraft position ' + str(idx) + ': ' + str(planetFixedPos))

            # Get the access indicators for each ground station
            accessIndicator1 = self.boulderGSLogs[idx].hasAccess[
                -int(self.step_duration) :
            ]
            accessIndicator2 = self.merrittGSLogs[idx].hasAccess[
                -int(self.step_duration) :
            ]
            accessIndicator3 = self.singaporeGSLogs[idx].hasAccess[
                -int(self.step_duration) :
            ]
            accessIndicator4 = self.weilheimGSLogs[idx].hasAccess[
                -int(self.step_duration) :
            ]
            accessIndicator5 = self.santiagoGSLogs[idx].hasAccess[
                -int(self.step_duration) :
            ]
            accessIndicator6 = self.dongaraGSLogs[idx].hasAccess[
                -int(self.step_duration) :
            ]
            accessIndicator7 = self.hawaiiGSLogs[idx].hasAccess[
                -int(self.step_duration) :
            ]

            transmitterBaud = self.transmitterLogs[idx].baudRate[
                -int(self.step_duration) :
            ]

            # Compute the image tuples and which targets have been downlinked and imaged
            image_tuples, image_tuples_norm = self.compute_image_tuples(
                planetFixedPos, planetFixedVel, idx
            )

            # Full observations (non-normalized), all rounded to five decimals
            obs_full[idx, 0:22] = np.hstack(
                np.around(
                    [
                        planetFixedPos[0],
                        planetFixedPos[1],
                        planetFixedPos[2],
                        planetFixedVel[0],
                        planetFixedVel[1],
                        planetFixedVel[2],
                        np.linalg.norm(attErr),
                        np.linalg.norm(attRate),
                        wheelSpeeds[0],
                        wheelSpeeds[1],
                        wheelSpeeds[2],
                        storedCharge / 3600.0,
                        eclipseIndicator,
                        storageLevel,
                        np.sum(transmitterBaud) * self.dynRate / 8e6,
                        np.sum(accessIndicator1) * self.dynRate,
                        np.sum(accessIndicator2) * self.dynRate,
                        np.sum(accessIndicator3) * self.dynRate,
                        np.sum(accessIndicator4) * self.dynRate,
                        np.sum(accessIndicator5) * self.dynRate,
                        np.sum(accessIndicator6) * self.dynRate,
                        np.sum(accessIndicator7) * self.dynRate,
                    ],
                    decimals=5,
                )
            )

            # Check if there is a switch in targets
            self.current_tgt_indices.append(self.check_target_switch(idx))

            # Add the target tuples
            for idx2 in range(self.n_targets):
                obs_full[
                    idx,
                    (22 + idx2 * self.target_tuple_size) : (
                        22 + (idx2 + 1) * self.target_tuple_size
                    ),
                ] = np.around(
                    image_tuples[
                        self.current_tgt_indices[-1][idx2]
                        * self.target_tuple_size : (
                            self.current_tgt_indices[-1][idx2] * self.target_tuple_size
                            + self.target_tuple_size
                        )
                    ]
                )

            # Normalized observations, pull things from dictionary for readability
            transmitterBaudRate = self.initial_conditions[str(idx)][
                "transmitterBaudRate"
            ]
            batteryStorageCapacity = self.initial_conditions[str(idx)][
                "batteryStorageCapacity"
            ]
            dataStorageCapacity = self.initial_conditions[str(idx)][
                "dataStorageCapacity"
            ]

            # Normalized observations, all rounded to five decimals
            obs_norm[idx, 0:22] = np.hstack(
                np.around(
                    [
                        planetFixedPos[0] / self.nominal_radius,
                        planetFixedPos[1] / self.nominal_radius,
                        planetFixedPos[2] / self.nominal_radius,
                        planetFixedVel[0] / np.linalg.norm(planetFixedVel[0:3]),
                        planetFixedVel[1] / np.linalg.norm(planetFixedVel[0:3]),
                        planetFixedVel[2] / np.linalg.norm(planetFixedVel[0:3]),
                        np.linalg.norm(attErr),
                        np.linalg.norm(attRate),
                        wheelSpeeds[0]
                        / (self.initial_conditions[str(idx)]["maxSpeed"] * mc.RPM),
                        wheelSpeeds[1]
                        / (self.initial_conditions[str(idx)]["maxSpeed"] * mc.RPM),
                        wheelSpeeds[2]
                        / (self.initial_conditions[str(idx)]["maxSpeed"] * mc.RPM),
                        storedCharge / batteryStorageCapacity,
                        eclipseIndicator,
                        storageLevel / dataStorageCapacity,
                        np.sum(transmitterBaud)
                        * self.dynRate
                        / (transmitterBaudRate * self.step_duration),
                        np.sum(accessIndicator1) * self.dynRate / self.step_duration,
                        np.sum(accessIndicator2) * self.dynRate / self.step_duration,
                        np.sum(accessIndicator3) * self.dynRate / self.step_duration,
                        np.sum(accessIndicator4) * self.dynRate / self.step_duration,
                        np.sum(accessIndicator5) * self.dynRate / self.step_duration,
                        np.sum(accessIndicator6) * self.dynRate / self.step_duration,
                        np.sum(accessIndicator7) * self.dynRate / self.step_duration,
                    ],
                    decimals=5,
                )
            )

            # Add the normalized imaged tuples
            for idx2 in range(self.n_targets):
                obs_norm[
                    idx,
                    (22 + idx2 * self.target_tuple_size) : (
                        22 + (idx2 + 1) * self.target_tuple_size
                    ),
                ] = np.around(
                    image_tuples_norm[
                        self.current_tgt_indices[-1][idx2]
                        * self.target_tuple_size : (
                            self.current_tgt_indices[-1][idx2] * self.target_tuple_size
                            + self.target_tuple_size
                        )
                    ],
                    decimals=5,
                )

            # Check if crashed into Earth
            if np.linalg.norm(inertialPos) < (orbitalMotion.REQ_EARTH / 1000.0):
                self.sim_over = True

            # Update performance metrics for the sc
            self.total_downlinked[idx] += -obs_full[idx, 14]
            self.total_access[idx] += max(obs_full[idx, 15:22])
            self.utilized_access[idx] += (
                np.sum(transmitterBaud)
                * self.dynRate
                / self.initial_conditions[str(idx)]["transmitterBaudRate"]
            )

            # Clear the logs
            if self.clear_logs:
                self.boulderGSLogs[idx].clear()
                self.merrittGSLogs[idx].clear()
                self.singaporeGSLogs[idx].clear()
                self.weilheimGSLogs[idx].clear()
                self.santiagoGSLogs[idx].clear()
                self.dongaraGSLogs[idx].clear()
                self.hawaiiGSLogs[idx].clear()
                self.transmitterLogs[idx].clear()
                self.storageUnitLogs[idx].clear()

        self.obs_full = obs_full
        self.obs = obs_norm

        return self.obs, self.sim_over, self.obs_full

    def close_gracefully(self):
        """
        makes sure spice gets shut down right when we close.
        :return:
        """
        self.EnvModel.gravFactory.unloadSpiceKernels()
        self.EnvModel.gravFactory.spiceObject.unloadSpiceKernel(
            self.EnvModel.gravFactory.spiceObject.SPICEDataPath, "de430.bsp"
        )
        self.EnvModel.gravFactory.spiceObject.unloadSpiceKernel(
            self.EnvModel.gravFactory.spiceObject.SPICEDataPath, "naif0012.tls"
        )
        self.EnvModel.gravFactory.spiceObject.unloadSpiceKernel(
            self.EnvModel.gravFactory.spiceObject.SPICEDataPath, "de-403-masses.tpc"
        )
        self.EnvModel.gravFactory.spiceObject.unloadSpiceKernel(
            self.EnvModel.gravFactory.spiceObject.SPICEDataPath, "pck00010.tpc"
        )

        return

    def compute_image_tuples(self, r_BN_N, v_BN_N, sc_idx):
        """
        Computes the self.n_images image state tuples
        0-2: S/c Hill-Frame Position
        3: Priority
        4: Imaged?
        5: Downlinked?
        :return image_tuples: image_tuple_size nparray
        :return image_tuples_norm: image_tuple_size nparray
        """
        # Initialize the image tuple array
        image_tuples = np.zeros(
            self.target_tuple_size
            * self.initial_conditions[str(sc_idx)]["transmitterNumBuffers"]
        )
        image_tuples_norm = np.zeros(
            self.target_tuple_size
            * self.initial_conditions[str(sc_idx)]["transmitterNumBuffers"]
        )

        # Grab the targets, priorities, and update whether or not target has been imaged
        # or downlinked
        targets = np.transpose(
            np.array(self.initial_conditions[str(sc_idx)]["targetPositions"])
        )  # SC-specific target locations
        target_indices = self.initial_conditions[str(sc_idx)][
            "targetIndices"
        ]  # Global target indices
        priorities = self.initial_conditions["env_params"]["globalPriorities"][
            target_indices
        ]

        # Compute the inertial-to-Hill DCM
        dcm_HN = orbitalMotion.hillFrame(r_BN_N, v_BN_N)

        # Loop through each target to construct the state tuple
        for idx2 in range(
            self.initial_conditions[str(sc_idx)]["transmitterNumBuffers"]
        ):
            idx_start = idx2 * self.target_tuple_size

            # Grab the PCPF position of the target
            r_TN_N = targets[:, idx2]

            # Compute the position of the target wrt the spacecraft
            r_TB_N = np.subtract(r_TN_N, r_BN_N)

            # Transform to Hill-frame components
            r_TB_H = np.matmul(dcm_HN, r_TB_N)

            # Normalize
            r_TB_H_norm = r_TB_H / self.initial_conditions["env_params"]["planetRadius"]

            # Add to non-normalized image tuples
            image_tuples[idx_start : idx_start + 3] = r_TB_H

            # Add to normalized image tuples
            image_tuples_norm[idx_start : idx_start + 3] = r_TB_H_norm

            # Add the priorities
            image_tuples[idx_start + 3] = priorities[idx2]
            image_tuples_norm[idx_start + 3] = 1 / priorities[idx2]

        return image_tuples, image_tuples_norm

    def update_imaged_targets(self, sc_idx):
        """
        Updates which targets have been imaged and which have been downlinked
        :return downlinked: 1 x n_targets np.array of downlinked targets
        :return imaged: 1 x n_targets np.array of imaged targets
        """
        # Initialize list of targets that were just downlinked or imaged, helpful for
        # reward computation
        downlinked = []
        imaged = []

        # Pull the data log - check that it pulled correctly
        if self.initialized:
            storedData = self.storageUnitLogs[sc_idx].storedData
            if len(storedData.shape) == 1:
                storedData = None
        else:
            storedData = None

        # Loop through the global target indices for this spacecraft to determine if it
        # has been imaged or not
        for local_idx, target_idx in enumerate(
            self.initial_conditions[str(sc_idx)]["targetIndices"]
        ):
            # Check for an update
            if self.check_image_update(
                local_idx, storedData, target_idx in self.imaged_targets[sc_idx]
            ):
                self.global_target_log[target_idx].append(
                    {"spacecraft": sc_idx, "time": self.simTime}
                )
                self.imaged_targets[sc_idx].append(target_idx)
                self.known_imaged_targets[sc_idx].append(target_idx)
                imaged.append(target_idx)

            # check for update
            if self.check_downlink_update(local_idx, storedData):
                self.downlinked_targets[sc_idx].append(target_idx)
                self.known_downlinked_targets[sc_idx].append(target_idx)
                downlinked.append(target_idx)

        return downlinked, imaged

    def check_image_update(self, idx, storedData, alreadyimaged):
        """
        Checks the storageUnitLog to see if data was added or not
        :param idx: index of target
        :param storedData: pulled log of storage unit
        :return: 1 if data was added since the last decision interval, 0 otherwise
        """
        if storedData is not None:
            if storedData[-1, idx] > 0 and not alreadyimaged:
                return 1
            else:
                return 0
        else:
            return 0

    def check_downlink_update(self, idx, storedData):
        """
        Checks the storageUnitLogs to see if an image was downlinked
        :param idx: index of target
        :param storedData: pulled log of storage unit
        :return: 1 if data was added, 0 otherwise
        """
        # If the partition has downlinked data greater than or equal to an image size,
        # the image has been downlinked
        if storedData is not None:
            if (
                storedData[-int(self.step_duration / self.dynRate), idx]
                - storedData[-1, idx]
            ) > 0:
                return 1
            else:
                return 0
        else:
            return 0

    def check_target_switch(self, sc_idx):
        """
        Grabs the index(s) of the next upcoming target(s)
        :return upcoming_tgts: 1 x n_targets nparray of upcoming local tgt indices
        """
        times = self.initial_conditions[str(sc_idx)]["target_times"]
        global_tgts = self.initial_conditions[str(sc_idx)]["targetIndices"]
        upcoming_tgts = []
        last_tgt = len(times) - 1
        for local_idx, time in enumerate(times):
            # If less than simTime, add to upcoming targets
            if self.simTime < time:
                if global_tgts[local_idx] not in self.known_imaged_targets[sc_idx]:
                    upcoming_tgts.append(local_idx)
                    last_tgt = local_idx

        # Check that the list has at least as many upcoming targets as n_targets
        # (num in action space)
        # If not, backfill with last target
        if (
            len(upcoming_tgts)
            < self.initial_conditions[str(sc_idx)]["transmitterNumBuffers"]
        ):
            for tgt in range(self.n_targets - len(upcoming_tgts)):
                # Append the last target
                upcoming_tgts.append(last_tgt)

        return upcoming_tgts

    def set_up_comms(self):
        """
        Complete any preparatory steps for the comm method.
        - none: none
        - los: none
        - los-multi: none
        - free: link all sats to share same known lists
        """
        if self.comm_method == "none":
            pass  # targets only update own list
        elif self.comm_method == "los":
            pass
        elif self.comm_method == "los-multi":
            pass
        elif self.comm_method == "free":  # make all sats point to same list
            global_imaged = []
            global_downlinked = []
            for sc_idx in range(self.n_spacecraft):
                self.known_imaged_targets[sc_idx] = global_imaged
                self.known_downlinked_targets[sc_idx] = global_downlinked

    def communicate(self):
        """
        Share information between satellites based on the comm method.
        - none: none
        - los: merge lists from any satellites with los communication
        - los-multi: merge lists from any satellites in a los-connected graph
        - free: implicitly shared through shared known lists
        """
        if self.comm_method == "none":
            pass  # targets only update own list
        elif self.comm_method == "los":
            commlinks = 0
            old_know_imaged = deepcopy(self.known_imaged_targets)
            old_know_downlinked = deepcopy(self.known_downlinked_targets)
            for sc_idx in range(self.n_spacecraft):
                for comm_idx in range(self.n_spacecraft):
                    if sc_idx != comm_idx and any(
                        self.losLogs[sc_idx][comm_idx].hasAccess[
                            -int(self.step_duration / self.dynRate) :
                        ]
                    ):
                        self.known_imaged_targets[sc_idx] = list(
                            set(
                                self.known_imaged_targets[sc_idx]
                                + old_know_imaged[comm_idx]
                            )
                        )
                        self.known_downlinked_targets[sc_idx] = list(
                            set(
                                self.known_downlinked_targets[sc_idx]
                                + old_know_downlinked[comm_idx]
                            )
                        )
                        commlinks += 1
                        if self.clear_logs:  # Clear the logs
                            self.losLogs[sc_idx][comm_idx].clear()
        elif self.comm_method == "los-multi":
            connectivity = np.array(
                [
                    [
                        any(
                            self.losLogs[sc_idx][comm_idx].hasAccess[
                                -int(self.step_duration / self.dynRate) :
                            ]
                        )
                        if comm_idx != sc_idx
                        else False
                        for comm_idx in range(self.n_spacecraft)
                    ]
                    for sc_idx in range(self.n_spacecraft)
                ]
            )
            n_components, labels = connected_components(connectivity, directed=False)
            for comp in range(n_components):
                sc_idxs = np.where(labels == comp)[0]
                if sc_idxs.size > 1:
                    combined_imaged = []
                    combined_downlinked = []
                    [
                        combined_imaged.extend(self.known_imaged_targets[sc_idx])
                        for sc_idx in sc_idxs
                    ]
                    [
                        combined_downlinked.extend(
                            self.known_downlinked_targets[sc_idx]
                        )
                        for sc_idx in sc_idxs
                    ]
                    combined_imaged = list(set(combined_imaged))
                    combined_downlinked = list(set(combined_downlinked))
                    for sc_idx in sc_idxs:
                        self.known_imaged_targets[sc_idx] = copy(combined_imaged)
                        self.known_downlinked_targets[sc_idx] = copy(
                            combined_downlinked
                        )
            if self.clear_logs:  # Clear the logs
                for sc_idx in range(self.n_spacecraft):
                    [
                        self.losLogs[sc_idx][comm_idx].clear()
                        for comm_idx in range(self.n_spacecraft)
                        if comm_idx != sc_idx
                    ]
        elif self.comm_method == "free":
            pass  # all sats share same known_imaged list so no sharing method needed