Source code for nexoclom.Input

"""Read the model inputs from a file and create the Input object.

The Input object is build from smaller objects defining different model
options.

Geometry
    Defines the Solar System geometry for the Input.

SurfaceInteraction
    Defines the surface interactions.

Forces
    Set which forces act on model particles.

SpatialDist
    Define the initial spatial distribution of particles.

SpeedDist
    Define the initial speed distribution of particles.

AngularDist
    Define the initial angular distribtuion of particles.

Options
    Configure other model parameters
"""
import os
import os.path
import numpy as np
import sys
import pandas as pd
import logging
from astropy.time import Time
import astropy.units as u
from .Output import Output
from .configure_model import configfile
from .database_connect import database_connect
from .input_classes import (Geometry, SurfaceInteraction, Forces, SpatialDist,
                            SpeedDist, AngularDist, Options)
from .produce_image import ModelImage
from .LOSResult import LOSResult


[docs]class Input: def __init__(self, infile): """Read the input options from a file. **Parameters** infile Plain text file containing model input parameters. See :doc:`inputfiles` for a description of the input file format. **Class Attributes** * geometry * surface_interaction * forces * spatialdist * speeddist * angulardist * options """ # Read the configuration file self._savepath = configfile() # Read in the input file: self._inputfile = infile params = [] if os.path.isfile(infile): # Remove everything in the line after a comment character for line in open(infile, 'r'): if ';' in line: line = line[:line.find(';')] elif '#' in line: line = line[:line.find('#')] else: pass if line.count('=') == 1: param_, val_ = line.split('=') if param_.count('.') == 1: sec_, par_ = param_.split('.') params.append((sec_.casefold().strip(), par_.casefold().strip(), val_.casefold().strip())) else: pass else: pass else: raise FileNotFoundError(infile) def extract_param(tag): return {b:c for (a,b,c) in params if a == tag} self.geometry = Geometry(extract_param('geometry')) self.surfaceinteraction = SurfaceInteraction(extract_param( 'surfaceinteraction')) self.forces = Forces(extract_param('forces')) self.spatialdist = SpatialDist(extract_param('spatialdist')) self.speeddist = SpeedDist(extract_param('speeddist')) self.angulardist = AngularDist(extract_param('angulardist')) self.options = Options(extract_param('options')) def __repr__(self): return self.__str__() def __str__(self): result = (self.geometry.__str__() + '\n' + self.surfaceinteraction.__str__() + '\n' + self.forces.__str__() + '\n' + self.spatialdist.__str__() + '\n' + self.speeddist.__str__() + '\n' + self.angulardist.__str__() + '\n' + self.options.__str__()) return result
[docs] def search(self): """ Search the database for previous model runs with the same inputs. See :doc:`searchtolerances` for tolerances used in searches. **Parameters** No parameters. **Returns** * A list of filenames corresponding to the inputs. * Number of packets contained in those saved outputs. * Total modeled source rate. """ geo_id = self.geometry.search() sint_id = self.surfaceinteraction.search() for_id = self.forces.search() spat_id = self.spatialdist.search() spd_id = self.speeddist.search() ang_id = self.angulardist.search() opt_id = self.options.search() if None in [geo_id, sint_id, for_id, spat_id, spd_id, ang_id, opt_id]: return [], 0., 0. else: query = f'''SELECT idnum, filename, npackets, totalsource FROM outputfile WHERE geo_type = '{self.geometry.type}' and geo_id = {geo_id} and sint_type = '{self.surfaceinteraction.sticktype}' and sint_id = {sint_id} and force_id = {for_id} and spatdist_type = '{self.spatialdist.type}' and spatdist_id = {spat_id} and spddist_type = '{self.speeddist.type}' and spddist_id = {spd_id} and angdist_type = '{self.angulardist.type}' and angdist_id = {ang_id} and opt_id = {opt_id}''' with database_connect() as con: result = pd.read_sql(query, con) return (result.filename.to_list(), result.npackets.sum(), result.totalsource.sum())
[docs] def run(self, npackets, packs_per_it=None, overwrite=False, compress=True): """Run the nexoclom model with the current inputs. **Parameters** npackets Number of packets to simulate packs_per_it Maximum number of packets to run at one time. Default = 1e5 in constant step-size mode; 1e6 in adaptive step-size mode. overwrite Erase any files matching the current inputs that exist. Default = False compress Remove packets with frac=0 from the outputs to reduce file size. Default = True **Outputs** Nothing is returned, but model runs are saved and cataloged. """ # Configure the logger # Note: The logfile name will be changed to match the outputfile name # and stored in the Output object. # logger = logging.getLogger() # logger.setLevel(logging.INFO) # log_file_handler = logging.FileHandler('log.out', 'w') # logger.addHandler(log_file_handler) # out_handler = logging.StreamHandler(sys.stdout) # logger.addHandler(out_handler) # fmt = logging.Formatter('%(levelname)s: %(msg)s') # log_file_handler.setFormatter(fmt) # out_handler.setFormatter(fmt) t0_ = Time.now() # logger.info(f'Starting at {t0_}') print(f'Starting at {t0_}') if len(self.geometry.planet) != 1: # logger.error('Gravity and impact check not working for ' # 'planets with moons.') sys.exit() # Determine how many packets have already been run if overwrite: self.delete_files() totalpackets = 0 else: outputfiles, totalpackets, _ = self.search() # logger.info(f'Found {len(outputfiles)} files with {totalpackets} ' # 'packets.') print(f'Found {len(outputfiles)} files with {totalpackets} ' 'packets.') npackets = int(npackets) ntodo = npackets - totalpackets if ntodo > 0: if (packs_per_it is None) and (self.options.step_size == 0): packs_per_it = 1000000 elif packs_per_it is None: packs_per_it = (1e8 * self.options.step_size / self.options.endtime.value) else: pass packs_per_it = int(np.min([ntodo, packs_per_it])) # Determine how many iterations are needed nits = int(np.ceil(ntodo/packs_per_it)) # logger.info('Running Model') # logger.info(f'Will complete {nits} iterations of {packs_per_it} ' # 'packets.') print('Running Model') print(f'Will complete {nits} iterations of {packs_per_it} packets.') for _ in range(nits): tit0_ = Time.now() # logger.info(f'Starting iteration #{_+1} of {nits}') print(f'Starting iteration #{_+1} of {nits}') # Create an output object Output(self, packs_per_it, compress=compress) #logger=logger) # Just run and save the model when output is created # No reason to explicitly call run tit1_ = Time.now() # logger.info(f'Completed iteration #{_+1} in ' # f'{(tit1_ - tit0_).sec} seconds.') print(f'Completed iteration #{_+1} in ' f'{(tit1_ - tit0_).sec} seconds.') else: pass t2_ = Time.now() dt_ = (t2_-t0_).sec if dt_ < 60: dt_ = f'{dt_} sec' elif dt_ < 3600: dt_ = f'{dt_/60} min' else: dt_ = f'{dt_/3600} hr' # logger.info(f'Model run completed in {dt_} at {t2_}.') # out_handler.close() print(f'Model run completed in {dt_} at {t2_}.')
[docs] def produce_image(self, format_, filenames=None, overwrite=False): return ModelImage(self, format_, filenames=filenames, overwrite=overwrite)
def line_of_sight(self, data, quantity, dphi=3*u.deg, filenames=None, overwrite=False): return LOSResult(self, data, quantity, dphi=dphi, filenames=filenames, overwrite=overwrite) def delete_files(self): """Delete output files and remove them from the database. **Parameters** filelist List of files to remove. This can be found with Inputs.search() **Returns** No outputs. """ filelist, _, _ = self.search() with database_connect() as con: cur = con.cursor() for f in filelist: # Delete the file print(f'Deleting {f}') if os.path.exists(f): os.remove(f) # Remove from database cur.execute('''SELECT idnum FROM outputfile WHERE filename = %s''', (f,)) idnum = cur.fetchone()[0] cur.execute('''DELETE FROM outputfile WHERE idnum = %s''', (idnum,)) cur.execute('''SELECT idnum, filename FROM modelimages WHERE out_idnum = %s''', (idnum,)) for mid, mfile in cur.fetchall(): cur.execute('''DELETE from modelimages WHERE idnum = %s''', (mid,)) if os.path.exists(mfile): os.remove(mfile)
# cur.execute('''SELECT idnum, filename FROM uvvsmodels # WHERE out_idnum = %s''', (idnum,)) # for mid, mfile in cur.fetchall(): # cur.execute('''DELETE from uvvsmodels # WHERE idnum = %s''', (mid,)) # if os.path.exists(mfile): # os.remove(mfile)