#!/usr/bin/env python3 # # To launch this agent from the root of Pyros: # # Linux console: # cd /srv/develop/pyros/docker # ./PYROS_DOCKER_START.sh # ./PYROS_RUN_WEBSERVER_ONLY -o tnc -fg # cd .. # ./PYROS start -o tnc -fg -a A_Scheduler # # Launch from Power Shell: # To go from docker to Powershell: pyros_user@ORION:~/app$ exit (or Ctrl+d) # Prompt is now PS ...> # cd \srv\develop\pyros # .\PYROS -t new-start -o tnc -fg -a A_Scheduler # # Launch from docker: # To go from Powershell to docker: PS ...> .\PYROS_DOCKER_SHELL # Prompt is now pyros_user@ORION:~/app$ # ./PYROS -t new-start -o tnc -fg -a A_Scheduler # # To use debug # ./PYROS -d -t new-start -o tnc -fg -a A_Scheduler # # ./PYROS -d -t start -o tnc -fg A_Scheduler # --------------------------------------------------- import sys import time import argparse import os import pickle import socket import yaml pwd = os.environ['PROJECT_ROOT_PATH'] if pwd not in sys.path: sys.path.append(pwd) short_paths = ['src', 'src/core/pyros_django'] for short_path in short_paths: path = os.path.abspath(os.path.join(pwd, short_path)) if path not in sys.path: sys.path.insert(0, path) #from src.core.pyros_django.majordome.agent.Agent import Agent, build_agent, log, parse_args from majordome.agent.Agent import Agent, build_agent, log, parse_args from seq_submit.models import Sequence from user_mgmt.models import Period, ScientificProgram, SP_Period from scp_mgmt.models import Quota from scheduling.models import PredictiveSchedule, EffectiveSchedule # = Specials import glob import shutil import guitastro import datetime from decimal import Decimal import zoneinfo import numpy as np from pyros_api import PyrosAPI class A_Scheduler(Agent): DPRINT = True # - Sampling of the night arrays (bins/night) BINS_NIGHT = 86400 # - status of the sequence after schedule computation SEQ_NOT_PROCESSED = 0 SEQ_SCHEDULED = 1 SEQ_SCHEDULED_OVER_QUOTA = 2 SEQ_REJECTED_NO_QUOTA_ENOUGH = -1 SEQ_REJECTED_NO_SLOT_AVAILABLE = -2 # - enum of the matrix line SEQ_K = 0 SEQ_SEQ_ID = 1 SEQ_KOBS0 = 2 SEQ_SP_ID = 3 SEQ_PRIORITY = 4 SEQ_DURATION = 5 SEQ_STATUS = 6 NB_SEQ = 7 # - All possible running states RUNNING_NOTHING = 0 RUNNING_SCHEDULE_PROCESSING = 1 _AGENT_SPECIFIC_COMMANDS = { # Format : “cmd_name” : (timeout, exec_mode) "do_compute_schedule_1" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), "do_create_seq_1" : (60, Agent.EXEC_MODE.SEQUENTIAL, ''), } # Test scenario to be executed (option -t) # "self do_stop_current_processing" # AgentCmd.CMD_STATUS_CODE.CMD_EXECUTED _TEST_COMMANDS_LIST = [ # Format : ("self cmd_name cmd_args", timeout, "expected_result", expected_status), (True, "self do_create_seq_1 6", 200, '', Agent.CMD_STATUS.CMD_EXECUTED), (True, "self do_stop asap", 500, "STOPPING", Agent.CMD_STATUS.CMD_EXECUTED), ] """ ================================================================= Methods running inside main thread ================================================================= """ def __init__(self, name:str=None,simulated_computer=None): if name is None: name = self.__class__.__name__ super().__init__(simulated_computer=simulated_computer) def _init(self): super()._init() log.debug("end super init()") log.info(f"self.TEST_MODE = {self.TEST_MODE}") # === Get the config object self.config = self._oc['config'] self.pconfig = self._oc['pyros_config'] # === Get agent_alias hostname = socket.gethostname() log.info(f"{hostname=}") log.info(f"{self.name=}") agent_alias = self.config.get_agent_real_name(self.name, hostname) log.info(f"{agent_alias=}") # === Get all file contexts from pyros config self._fn = self.config.fn log.info(f"=== List of file name contexts available for the unit") self.check_contexts(True) log.info(f"{self._fn.longitude=}") # TBD duskelev a parametrer from obsconfig (yml) self._duskelev = -7 # === Status of routine processing self._routine_running = self.RUNNING_NOTHING log.debug("end init()") ##### TBD suppress redondant paths in print(f"=>=>=> {sys.path=}") # Note : called by _routine_process() in Agent # @override def _routine_process_iter_start_body(self): log.debug("in routine_process_before_body()") # Note : called by _routine_process() in Agent # @override def _routine_process_iter_end_body(self): log.debug("in routine_process_after_body()") # TODO EP est-ce utile ? if self._routine_running == self.RUNNING_NOTHING: # Get files to process # - Thread TODO self._routine_running = self.RUNNING_SCHEDULE_PROCESSING self.do_compute_schedule_1() """ ================================================================= Methods of specific commands ================================================================= """ def do_create_seq_1(self, nb_seq:int): """Create sequences to debug :raises ExceptionType: Some multi-line exception description. """ try: self._create_seq_1(nb_seq) except Exception as e: self.dprint(f"ERROR {e}") def do_compute_schedule_1(self): """Compute a schedule According the current time, select the night directory. List the *.p file list (.p for sequences) Read the *.p, *.f file contents (.f for ephemeris) Compute the schedule Output is a matrix to unpack in the database. Each line of the matrix is a sequence Columns are defined by the enum SEQ_* (see the python code itself). """ try: self._compute_schedule_1() except Exception as e: self.dprint(f"ERROR {e}") """ ================================================================= Methods called by commands or routine. Overload these methods ================================================================= # --- # osp = ScientificProgram.objects.get(id=scientific_program_id) # --- ospperiod is the SP object # ospperiod = SP_Period.objects.get(period = period_id, scientific_program = osp) # print(f"dir(ospperiod)={dir(ospperiod)}") # dir(spperiod)=['DoesNotExist', # 'IS_VALID', 'IS_VALID_ACCEPTED', 'IS_VALID_REJECTED', # 'MultipleObjectsReturned', 'SP_Period_Guests', 'SP_Period_Users', # 'STATUSES', 'STATUSES_ACCEPTED', 'STATUSES_DRAFT', # 'STATUSES_EVALUATED', 'STATUSES_REJECTED', 'STATUSES_SUBMITTED', # 'VISIBILITY_CHOICES', 'VISIBILITY_NO', 'VISIBILITY_YES', # 'VOTES', 'VOTES_NO', 'VOTES_TO_DISCUSS', 'VOTES_YES', # 'can_submit_sequence', 'check', 'clean', 'clean_fields', # 'date_error_message', 'delete', 'from_db', 'full_clean', # 'get_constraints', 'get_deferred_fields', 'get_is_valid_display', # 'get_public_visibility_display', 'get_status_display', # 'get_vote_referee1_display', 'get_vote_referee2_display', # 'id', 'is_currently_active', 'is_valid', 'objects', # 'over_quota_duration', 'over_quota_duration_allocated', # 'over_quota_duration_remaining', 'period', 'period_id', # 'pk', 'prepare_database_save', 'priority', 'public_visibility', # 'quota_allocated', 'quota_minimal', 'quota_nominal', # 'quota_remaining', 'reason_referee1', 'reason_referee2', # 'referee1', 'referee1_id', 'referee2', 'referee2_id', # 'refresh_from_db', 'save', 'save_base', 'scientific_program', # 'scientific_program_id', 'serializable_value', 'status', 'token', # 'token_allocated', 'token_remaining', 'unique_error_message', # 'validate_constraints', 'validate_unique', 'vote_referee1', # 'vote_referee2' """ def update_db_quota_sequence(self, sequence_id, quota_attributes): sequence = Sequence.objects.get(id=sequence_id) new_quota = Quota() new_quota.set_attributes_and_save(quota_attributes) sequence.quota = new_quota sequence.save() def _compute_schedule_1(self): """Simple scheduler based on selection-insertion one state algorithm. Quotas are available only fo the night. No token. """ t0 = time.time() self.DPRINT = True # =================== # --- Initializations # =================== # --- Get the incoming directory of the night info = self.get_infos() rootdir = info['rootdir'] subdir = info['subdir'] # --- Get the night night = info['night'] # --- Get ephemeris informations of the night and initialize quotas self.update_sun_moon_ephems() # Get quota for night oquota = Quota.objects.get(id_period=info["operiod"].id, night_id=info["night"]) # --- Build the wildcard to list the sequences wildcard = os.path.join(rootdir, subdir, "*.p") self.dprint(f"{wildcard=}") # --- List the sequences from the incoming directory seqfiles = glob.glob(wildcard) log.info(f"{len(seqfiles)} file sequences to process") # --- Initialize the predictive schedule from start of the night (=all the night) schedule_sequence_id = np.zeros(self.BINS_NIGHT, dtype=int) -1 schedule_binary = np.ones(self.BINS_NIGHT, dtype=int) schedule_visibility = np.zeros(self.BINS_NIGHT, dtype=float) schedule_order = np.zeros(self.BINS_NIGHT, dtype=int) -1 schedule_jd = np.zeros(self.BINS_NIGHT, dtype=float) schedule_scientific_programm_id = np.zeros(self.BINS_NIGHT, dtype=int) -1 # =========================================================================================================== # --- Initialize the predictive schedule by the effective schedule from start of the current instant (=index) # =========================================================================================================== try: last_schedule = EffectiveSchedule.objects.last() except EffectiveSchedule.DoesNotExist: self.dprint(f"No effective schedule in the database (table is void)") # --- Get the numpy matrix of the effective schedule from the database (via Json) if last_schedule != None: input_matrix = last_schedule.conv_numpy() # --- Unpack the matrix to effective schedule arrays schedule_eff_jd, schedule_eff_binary, schedule_eff_sequence_id, schedule_eff_scientific_programm_id, schedule_eff_order, schedule_eff_visibility = input_matrix # --- Get the index of the current instant in the night nownight, index = self._fn.date2night("now", self.BINS_NIGHT) self.dprint(f"{nownight=} {index=}") # --- Add all ever observed sequences from 0 to index if nownight == night and (index >= 0 or index < self.BINS_NIGHT): schedule_sequence_id[0:index] = schedule_eff_sequence_id[0:index] schedule_binary[0:index] = schedule_eff_binary[0:index] schedule_visibility[0:index] = schedule_eff_visibility[0:index] schedule_order[0:index] = schedule_eff_order[0:index] schedule_jd[0:index] = schedule_eff_jd[0:index] schedule_scientific_programm_id[0:index] = schedule_eff_scientific_programm_id[0:index] else: # --- Case when there is no effective schedule for this night print(f"No effective schedule for this night {night}") else: # --- Case of invalid entry in the database print(f"Invalid entry in the database") #print(f"{schedule_jd=}") # =================================================================== # --- Loop over the sequences of the night to extract useful infos # =================================================================== self.dprint("\n" + "="*70 + f"\n=== Read {len(seqfiles)} sequence files of the night {info['night']}\n" + "="*70 + "\n") sequence_infos = [] # --- Initialize the list of scientific_program_ids scientific_program_ids = [] kseq = 0 for seqfile in seqfiles: # --- seqfile = sequence file name kseq += 1 sequence_info = {} sequence_info['id'] = -1 # TBD replace by idseq of the database sequence_info['seqfile'] = seqfile sequence_info['error'] = "" sequence_info['kobs0'] = -1 # --- ephfile = ephemeris file name ephfile = os.path.splitext(seqfile)[0] + ".f" # --- If ephemeris file exists, read files if os.path.exists(ephfile): self.dprint(f"Read file {seqfile}") # --- seq_info = sequence dictionary seq_info = pickle.load(open(seqfile,"rb")) #print("="*20 + "\n" + f"{seq_info=}") # --- eph_info = ephemeris dictionary eph_info = pickle.load(open(ephfile,"rb")) #print("="*20 + "\n" + f"{eph_info=}") # --- self._fn.fcontext = "pyros_seq" param = self._fn.naming_get(seqfile) sequence_info['id'] = int(param['id_seq']) # --- scientific_program_id is an integer scientific_program_id = seq_info['sequence']['scientific_program'] # --- Dictionary of informations about the sequence sequence_info['seq_dico'] = seq_info # useful for duration # --- Search the last time when the start of the sequence is observable (visibility > 0) visibility_duration = eph_info['visibility_duration'] kobss = np.where(visibility_duration > 0) kobss = list(kobss[0]) if len(kobss) == 0: self.dprint(" Sequence has no visibility") sequence_info['error'] = f"Sequence has no visibility_duration" sequence_infos.append(sequence_info) continue # --- TODO manage the case the sequence is before the current time (because of the effective schedule) kobs0 = kobss[0] sequence_info['kobs0'] = kobs0 sequence_info['visibility'] = eph_info['visibility'] # total slots sequence_info['visibility_duration'] = visibility_duration # total slots - duration sequence_info['duration'] = seq_info['sequence']['duration'] sequence_info['scientific_program_id'] = scientific_program_id sequence_info["period"] = seq_info["sequence"]["period"] sequence_info["night_id"] = seq_info["sequence"]["night_id"] self.dprint(f" {scientific_program_id=} range to start={len(kobss)}") if scientific_program_id not in scientific_program_ids: scientific_program_ids.append(scientific_program_id) # --- TODO quota_attributes = {} quota_attributes["d_total"] = int(np.ceil(sequence_info["duration"])) quota_attributes["d_schedule"] = int(np.ceil(sequence_info["duration"])) quota_attributes["night_id"] = sequence_info["night_id"] quota_attributes["id_period"] = sequence_info["period"] self.update_db_quota_sequence(seq_info["sequence"]["id"], quota_attributes) else: sequence_info['error'] = f"File {ephfile} not exists" sequence_infos.append(sequence_info) try: schedule_jd = eph_info['jd'] except: pass # =================================================================== # --- Get informations of priority and quota from scientific programs # =================================================================== self.dprint("\n" + "="*70 + f"\n=== Get information from {len(scientific_program_ids)} scientific programs of the night\n" + "="*70 + "\n") scientific_program_infos = {} period_id = info['operiod'].id self.dprint(f"{scientific_program_ids=}") for scientific_program_id in scientific_program_ids: scientific_program_info = {} try: osp = ScientificProgram.objects.get(id=scientific_program_id) # --- ospperiod is the SP object ospperiod = SP_Period.objects.get(period = period_id, scientific_program = osp) scientific_program_info['priority'] = ospperiod.priority scientific_program_info['over_quota_duration'] = ospperiod.over_quota_duration scientific_program_info['over_quota_duration_allocated'] = ospperiod.over_quota_duration_allocated scientific_program_info['over_quota_duration_remaining'] = ospperiod.over_quota_duration_remaining scientific_program_info['quota_allocated'] = ospperiod.quota_allocated scientific_program_info['quota_minimal'] = ospperiod.quota_minimal scientific_program_info['quota_nominal'] = ospperiod.quota_nominal scientific_program_info['quota_remaining'] = ospperiod.quota_remaining scientific_program_info['token_allocated'] = ospperiod.token_allocated scientific_program_info['token_remaining'] = ospperiod.token_allocated except: # --- simulation scientific_program_info['priority'] = 0 if scientific_program_info['priority'] == 0: # --- simulation priority = 50 + scientific_program_id*5 scientific_program_info['priority'] = priority scientific_program_info['quota_allocated'] = 12000 scientific_program_info['quota_remaining'] = 12000 scientific_program_infos[str(scientific_program_id)] = scientific_program_info self.dprint(f"{scientific_program_id=} priority={scientific_program_info['priority']} quota={scientific_program_info['quota_remaining']}") # =================================================================== # --- Build the numpy matrix seqs to make rapid computations # =================================================================== self.dprint("\n" + "="*70 + f"\n=== Build the matrix for scheduling {len(sequence_infos)} sequences\n" + "="*70 + "\n") self.dprint("Order ID_seq K_start ID_sp Priority Duration Status\n") nseq = len(sequence_infos) if nseq == 0: self._routine_running = self.RUNNING_NOTHING return seqs = np.zeros((nseq, self.NB_SEQ), dtype=int) k = 0 for sequence_info in sequence_infos: if 'scientific_program_id' not in sequence_info.keys(): self.dprint(f"No scientific program for ID sequence {sequence_info['id']}") continue scientific_program_id = sequence_info['scientific_program_id'] scientific_program_info = scientific_program_infos[str(scientific_program_id)] priority = scientific_program_info['priority'] # Order of the following list refers to the enum seq = [ k, sequence_info['id'], sequence_info['kobs0'], scientific_program_id, priority, int(np.ceil(sequence_info['duration'])), self.SEQ_NOT_PROCESSED] self.dprint(f"{seq=}") seqs[k] = seq k += 1 seqs = seqs[:k] # --- Save the matrix sequence #print(f"{seqs=}") fpathname = os.path.join(rootdir, subdir, "scheduler_seq_matrix1.txt") np.savetxt(fpathname, seqs) # =================================================================== # --- Compute the matrix seq_sorteds (priority and chronology) # =================================================================== self.dprint("\n" + "="*70 + "\n=== Sort the matrix for scheduling by priority and chronology\n" + "="*70 + "\n") # --- Sort the matrix sequence: priority=SEQ_PRIORITY (decreasing -1) and then chronology=SEQ_KOBS0 (increasing +1) seq_sorteds = seqs[np.lexsort(([1,-1]*seqs[:,[self.SEQ_KOBS0, self.SEQ_PRIORITY]]).T)] # --- Save the matrix sequence self.dprint("Order ID_seq K_start ID_sp Priority Duration Status\n") self.dprint(f"{seq_sorteds=}") fpathname = os.path.join(rootdir, subdir, "scheduler_seq_matrix2.txt") np.savetxt(fpathname, seq_sorteds) # =================================================================== # --- Insert sequences in the schedule. Respecting priority and quota # =================================================================== self.dprint("\n" + "="*70 + "\n=== Insertion of the sequences in the schedule respecting priority and quota\n" + "="*70 + "\n") kseq_sorted = -1 for seq_sorted in seq_sorteds: kseq_sorted += 1 # --- Unpack the sequence k, sequence_id, kobs0, scientific_program_id, priority, duration, seq_status = seq_sorted # --- Get the quota remaining of the scientific program quota_remaining = scientific_program_infos[str(scientific_program_id)]['quota_remaining'] self.dprint('-'*70 + "\n" + f"Process {sequence_id=} {kobs0=} {duration=} sp_id={scientific_program_id} {quota_remaining=}") # --- Verify if duration < quota_remaining if duration > quota_remaining: # --- No remaining quota to insert this sequence self.dprint(f"{sequence_id=} cannot be inserted because no quota enough") seqs[k][self.SEQ_STATUS] = self.SEQ_REJECTED_NO_QUOTA_ENOUGH continue # --- Compute the remaining visibility and list (k1s) of the best observation start # =0 if not possible to start observation # =value with the highest value for the best observation start # --- Visibility*schedule_binary are transformed into binary sequence_info = sequence_infos[k] vis_binarys = sequence_info['visibility'].copy() * schedule_binary vis_binarys[vis_binarys > 0] = 1 # --- Cumulative sum + offset by -duration to prepare the start_binary computation obs_starts = np.cumsum(vis_binarys) obs_ends = obs_starts.copy() obs_ends[0:-duration] = obs_ends[duration:] obs_ends[-duration:] = 0 # --- Difference and binarisation to get starts with duration start_binary = obs_ends - obs_starts start_binary[start_binary < duration] = 0 start_binary[start_binary == duration] = 1 # --- Compute the remaining visibility (float) remaining_visibility = sequence_info['visibility'] * start_binary # --- Check the remaining visibility if np.sum(remaining_visibility) == 0: # --- No remaining slot to insert this sequence self.dprint(f"{sequence_id=} cannot inserted because no more slots available") seqs[k][self.SEQ_STATUS] = self.SEQ_REJECTED_NO_SLOT_AVAILABLE continue # --- From the index of the highest value of remaining visibility to the index of the lowest value of remaining visibility k1s = np.flip(np.argsort(remaining_visibility)) self.dprint(f"{k1s=} => Start elevation {sequence_info['visibility'][k1s[0]]:+.2f}") # --- Get k1 as the highest value of remaining visibility k1 = k1s[0] k2 = k1 + duration self.dprint(f"{k} : {sequence_id=} {scientific_program_id=} {priority=} inserted in the slot {k1=} {k2=} (remaining {quota_remaining - duration} s)") # --- Update the seqs matrix seqs[k][self.SEQ_STATUS] = self.SEQ_SCHEDULED # --- Update the schedule arrays schedule_sequence_id[k1:k2] = sequence_id schedule_binary[k1:k2] = 0 schedule_visibility[k1:k2] = sequence_info['visibility'][k1:k2] schedule_order[k1:k2] = kseq_sorted schedule_scientific_programm_id[k1:k2] = scientific_program_id # --- Update the scientific program dict quota_remaining -= duration scientific_program_infos[str(scientific_program_id)]['quota_remaining'] = quota_remaining # =================================================================== # --- Insert sequences in the schedule. Respecting priority but over quota # =================================================================== # self.dprint("\n" + "="*70 + "\n=== Insertion of the sequences in the schedule respecting priority but over quota\n" + "="*70 + "\n") # TBD # where are remaining free slots # scan sequences to insert in these free slots # =================================================================== # --- Save the schedule # =================================================================== self.dprint("\n" + "="*70 + "\n=== Save the schedule\n" + "="*70 + "\n") self.dprint("Order ID_seq K_start ID_sp Priority Duration Status\n") self.dprint(f"{seqs=}") # --- Prepare the output matrix ouput_matrix = np.array([schedule_jd, schedule_binary, schedule_sequence_id, schedule_scientific_programm_id, schedule_order, schedule_visibility]) # --- Save the numpy matrix in ASCII fpathname = os.path.join(rootdir, subdir, "scheduler_schedule.txt") np.savetxt(fpathname, ouput_matrix.T) # --- Save the numpy matrix in database (via Json) v = PredictiveSchedule.objects.last() if v == None: v = PredictiveSchedule() #log.info(f"{v=}") v.scheduler_matrix = ouput_matrix v.save() # --- Save the numpy matrix in database (via Json) v = EffectiveSchedule.objects.last() if v==None: v = EffectiveSchedule() v.scheduler_matrix = ouput_matrix v.save() # --- Update the running state self._routine_running = self.RUNNING_NOTHING log.info(f"_compute_schedule_1 finished in {time.time() - t0:.2f} seconds") def _create_seq_1(self, nb_seq: int): # delete all previous test seq Sequence.objects.filter(id__gte=9990000000).delete() t0 = time.time() self.dprint("Debut _create_seq_1") seq_template = yaml.safe_load(open("scheduler_seq_template.yml","r")) #{"simplified":True, 'sequence': {'id': 4, 'start_expo_pref': 'IMMEDIATE', 'pyros_user': 2, 'scientific_program': 1, 'name': 'seq_20230628T102140', 'desc': None, 'last_modified_by': 2, 'is_alert': False, 'status': 'TBP', 'with_drift': False, 'priority': None, 'analysis_method': None, 'moon_min': None, 'alt_min': None, 'type': None, 'img_current': None, 'img_total': None, 'not_obs': False, 'obsolete': False, 'processing': False, 'flag': None, 'period': 1, 'start_date': datetime.datetime(2023, 6, 28, 10, 21, 40, tzinfo=zoneinfo.ZoneInfo(key='UTC')), 'end_date': datetime.datetime(2023, 6, 28, 10, 21, 40, 999640, tzinfo=datetime.timezone.utc), 'tolerance_before': '1s', 'tolerance_after': '1min', 'duration': -1.0, 'submitted': False, 'config_attributes': {'tolerance_before': '1s', 'tolerance_after': '1min', 'target': 'RADEC 0H10M -15D', 'conformation': 'WIDE', 'layout': 'Altogether'}, 'ra': None, 'dec': None, 'complete': True, 'night_id': '20230627'}, 'albums': {'Altogether': {'plans': [{'id': 4, 'album': 4, 'duration': 0.0, 'nb_fnges': 1, 'config_attributes': {'binnings': {'binxy': [1, 1], 'readouttime': 6}, 'exposuretime': 1.0}, 'complete': True}]}}} # decode general variables info a dict info info = self.get_infos() rootdir = info['rootdir'] subdir = info['subdir'] # --- Read or create the sun ephemeris ephem_sun = self.ephem_target2night("sun") # --- Read or create the moon ephemeris ephem_moon = self.ephem_target2night("moon") # --- Prepare ephemeris object eph = guitastro.Ephemeris() eph.set_home(self.config.getHome()) # --- Horizon (TBD get from config) self.dprint("Debut _create_seq_1 Horizon") hor = guitastro.Horizon(eph.home) hor.horizon_altaz = self.config.getHorizonLine(self.config.unit_name) # --- Delete all existing *.p and *.f files in the night directory fn_param = { "period" : f"{info['period_id']}", "version": "1", "unit": self.config.unit_name, "date": info['night'], "id_seq": 0 } fname = self._fn.naming_set(fn_param) self.dprint(f":: {fname=}") seq_file = self._fn.join(fname) path_night = os.path.dirname(seq_file) cards = ['*.p', '*.f'] for card in cards: wildcard = os.path.join(path_night, card) seq_dfiles = glob.glob(wildcard) #print(f"::: {seq_dfiles=}") for seq_dfile in seq_dfiles: #print(f":::.1 : os.remove {seq_dfile=}") os.remove(seq_dfile) # --- Create new sequences for k in range(nb_seq): #print("B"*20 + f" {info['operiod'].id} {info['night']} {k}") if k < nb_seq/2: scientific_program = 0 else: scientific_program = 1 time.sleep(1) seq = seq_template.copy() print(f"{seq}") #seq['sequence']['config_attributes']['target'] = k # int # --- #start_expo_pref = "BESTELEV" #"IMMEDIATE" start_expo_pref = 0 # for bestelev 1, for immediate 0 start_date,_ = self._fn.night2date(info["night"]) start_date = start_date + 0.25 + (0.5*k) start_date_to_datetime = guitastro.Date(start_date).iso() #start_date = datetime.datetime(2023, 6, 28, 10, 21, 40) #end_date = datetime.datetime(2023, 6, 28, 10, 21, 40, 999640, tzinfo=datetime.timezone.utc) jd1 = Decimal('0E-8') jd2 = Decimal('0E-8') tolerance_before = '1s' tolerance_after = '5min' duration = 3000.0 target = f"RADEC {k}h {10+2*k}d" # --- seq['sequence']['start_expo_pref'] = start_expo_pref seq['sequence']['scientific_program'] = scientific_program seq['sequence']['start_date'] = start_date_to_datetime # seq['sequence']['jd1'] = jd1 # seq['sequence']['jd2'] = jd2 seq['sequence']['tolerance_before'] = tolerance_before seq['sequence']['tolerance_after'] = tolerance_after seq['sequence']['target'] = target # --- Build the path and file name of the sequence file seq["sequence"]["id"] = int("999" + f"{k:07d}") seq["sequence"]["name"] = "seq_"+str(seq["sequence"]["id"]) # Add sequence to db # with pyros_api script pyros_api = PyrosAPI(None) self._fn.fcontext = "pyros_seq_tmp" self._fn.extension = ".json" seq_fname = self._fn.join(str(seq["sequence"]["id"])) seq_file = open(seq_fname,"w") seq_file.write(yaml.dump(seq, sort_keys=False)) seq_file.close() response = pyros_api.submit_sequence_file(seq_fname) log.info(f"{response}") """ self.dprint(f"{k} : {self._fn.fcontext=}") self._fn.fname = self._fn.naming_set(fn_param) self.dprint(f"{k} : {self._fn.fname=}") seq_file = self._fn.join(self._fn.fname) self.dprint(f"{k} : {seq_file=}") # --- Build the path and file name of the ephemeris file eph_file = f"{seq_file[:-2]}.f" # --- Create directory if it doesn't exist self.dprint(f"{k} : {seq_file=}") os.makedirs(os.path.dirname(seq_file), exist_ok=True) # --- Compute the ephemeris of the sequence and manage errors #print(f"{k} : TRY") errors = [] try: ephem = eph.target2night(seq["sequence"]["config_attributes"]["target"], info['night'], ephem_sun, ephem_moon, preference=seq['sequence']['start_expo_pref'], duskelev=self._duskelev, horizon=hor, duration=duration) except ValueError: errors.append("Target value is not valid") except guitastro.ephemeris.EphemerisException as ephemException: errors.append(str(ephemException)) if len(errors) == 0 and np.sum(ephem["visibility"]) == 0 : errors.append("Target is not visible.") if len(errors) == 0: pickle.dump(ephem, open(eph_file,"wb")) pickle.dump(seq, open(seq_file,"wb")) #dprint(f"{errors=}") #dprint("C"*20) """ log.info(f"_create_seq_1 finished in {time.time() - t0:.2f} seconds") def load_sequence(self): sequence = "" return sequence def get_infos(self): self._fn.fcontext = "pyros_seq" rootdir = self._fn.rootdir operiod = Period.objects.exploitation_period() if operiod == None: log.info("No period valid in the database") self._routine_running = self.RUNNING_NOTHING return # retourne un str -> id de la période sous le format Pxxx period_id = operiod.get_id_as_str() night_id = self._fn.date2night("now") subdir = os.path.join(period_id, night_id) dico = {} dico['rootdir'] = rootdir dico['subdir'] = subdir dico['operiod'] = operiod # object dico['period_id'] = period_id # str formated (P000) dico['night'] = night_id # str (YYYYMMDD) return dico def dprint(self, *args, **kwargs): if self.DPRINT: log.info(*args, **kwargs) if __name__ == "__main__": agent = build_agent(A_Scheduler) print(agent) agent.run()