From fa8de6e6b8d2123f6ee0bcb53c514fcedc585359 Mon Sep 17 00:00:00 2001 From: Steven Murray Date: Fri, 22 May 2020 14:18:31 -0700 Subject: [PATCH] chore: add a lot of infrastructure files --- .flake8 | 36 +++++ .pre-commit-config.yaml | 33 +++++ hera_sim/VERSION | 1 - hera_sim/__init__.py | 18 ++- hera_sim/simulate.py | 314 ++++++++++++++++++++-------------------- hera_sim/version.py | 86 ----------- pyproject.toml | 3 +- setup.cfg | 108 ++++++++++++++ setup.py | 76 +--------- 9 files changed, 355 insertions(+), 320 deletions(-) create mode 100644 .flake8 create mode 100644 .pre-commit-config.yaml delete mode 100644 hera_sim/VERSION delete mode 100644 hera_sim/version.py create mode 100644 setup.cfg diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..cbf3f980 --- /dev/null +++ b/.flake8 @@ -0,0 +1,36 @@ +[flake8] +ignore = + E203 + E266 + E501 + W503 + F403 + F401 + E231 +max-line-length = 88 +# Should be 18. +max-complexity = 25 +exclude = + development/* +# Not ready for this yet. +per-file-ignores = + scripts/hmf: F821 + scripts/hmf-fit: F821 +select = B,C,E,F,W,T4,B9,D +rst-roles = + class + func + mod + data + const + meth + attr + exc + obj +rst-directives = + note + warning + versionadded + versionchanged + deprecated + seealso diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..7444b207 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +exclude: '^docs/conf.py' + +repos: +- repo: git://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 + hooks: + - id: trailing-whitespace + - id: check-added-large-files + - id: check-ast + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: check-merge-conflict + - id: mixed-line-ending + args: ['--fix=no'] +- repo: https://gitlab.com/pycqa/flake8 + rev: '3.8.1' # pick a git hash / tag to point to + hooks: + - id: flake8 + additional_dependencies: + - flake8-rst-docstrings +- repo: https://github.com/psf/black + rev: 19.10b0 + hooks: + - id: black +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.5.1 + hooks: + - id: rst-backticks diff --git a/hera_sim/VERSION b/hera_sim/VERSION deleted file mode 100644 index 3eefcb9d..00000000 --- a/hera_sim/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.0.0 diff --git a/hera_sim/__init__.py b/hera_sim/__init__.py index cab8d051..d06187d6 100644 --- a/hera_sim/__init__.py +++ b/hera_sim/__init__.py @@ -1,3 +1,15 @@ +try: + from importlib.metadata import version, PackageNotFoundError +except ImportError: + from importlib_metadata import version, PackageNotFoundError + +try: + __version__ = version(__name__) +except PackageNotFoundError: + print("package not found") + # package is not installed + pass + from . import __yaml_constructors from . import antpos from . import foregrounds @@ -7,16 +19,12 @@ from . import rfi from . import sigchain from hera_sim.visibilities import simulators -from . import version from . import eor from . import utils from . import simulate -from .cli import run # for testing purposes +from .cli import run # for testing purposes from .simulate import Simulator from .defaults import defaults from .components import SimulationComponent, registry from .components import list_discoverable_components from .interpolators import Tsky, Bandpass, Beam - -__version__ = version.version - diff --git a/hera_sim/simulate.py b/hera_sim/simulate.py index 148be95b..ef0fb226 100644 --- a/hera_sim/simulate.py +++ b/hera_sim/simulate.py @@ -10,27 +10,31 @@ import numpy as np from cached_property import cached_property -from pyuvdata import UVData, utils +from pyuvdata import UVData from astropy import constants as const from . import io from . import utils from .defaults import defaults -from .version import version +from . import __version__ from .components import SimulationComponent + # wrapper for the run_sim method, necessary for part of the CLI def _generator_to_list(func, *args, **kwargs): @functools.wraps(func) def new_func(*args, **kwargs): result = list(func(*args, **kwargs)) return None if result == [] else result + return new_func + class Simulator: """Class for managing a simulation. """ + def __init__(self, data=None, defaults_config=None, **kwargs): """Initialize a Simulator object. @@ -45,7 +49,7 @@ def __init__(self, data=None, defaults_config=None, **kwargs): self.extras = {} self._seeds = {} self._antpairpol_cache = {} - + # apply and activate defaults if specified if defaults_config: self.apply_defaults(defaults_config) @@ -92,10 +96,10 @@ def add(self, component, **kwargs): # find out whether the data application should be filtered vis_filter = kwargs.pop("vis_filter", None) - + # take out the seed_mode kwarg so as not to break initializor seed_mode = kwargs.pop("seed_mode", -1) - + # get the model for the desired component model, is_class = self._get_component(component) @@ -111,22 +115,24 @@ def add(self, component, **kwargs): # instantiate the class if the component is a class if is_class: model = model(**kwargs) - + # check that there isn't an issue with component ordering self._sanity_check(model) - + # re-add the seed_mode kwarg if it was specified if seed_mode != -1: kwargs["seed_mode"] = seed_mode - + # calculate the effect data = self._iteratively_apply( - model, add_vis=add_vis, ret_vis=ret_vis, - vis_filter=vis_filter, + model, + add_vis=add_vis, + ret_vis=ret_vis, + vis_filter=vis_filter, antpairpol_cache=antpairpol_cache, **kwargs ) - + # log the component and its kwargs, if added to data if add_vis: # note the filter used if any @@ -143,7 +149,7 @@ def add(self, component, **kwargs): # if we're not adding it, then we don't want to keep # the antpairpol cache _ = self._antpairpol_cache.pop(model_key) - + # return the data if desired if ret_vis: return data @@ -156,29 +162,29 @@ def get(self, component, ant1=None, ant2=None, pol=None): # XXX do we want to leave this check in there? if component not in self._components: raise AttributeError( - "You are trying to retrieve a component that has not " - "been simulated. Please check that the component you " - "are passing is correct. Consult the _components " - "attribute to see which components have been simulated " + "You are trying to retrieve a component that has not " + "been simulated. Please check that the component you " + "are passing is correct. Consult the _components " + "attribute to see which components have been simulated " "and which keys are provided." ) - - if ((ant1 is None) ^ (ant2 is None)): + + if (ant1 is None) ^ (ant2 is None): raise TypeError( - "You are trying to retrieve a visibility but have only " - "specified one antenna. This use is unsupported; please " + "You are trying to retrieve a visibility but have only " + "specified one antenna. This use is unsupported; please " "either specify an antenna pair or leave both as None." ) # retrieve the model model, is_class = self._get_component(component) - + # get the kwargs kwargs = self._components[component] - + # figure out whether or not to seed the rng seed_mode = kwargs.pop("seed_mode", None) - + # get the antpairpol cache antpairpol_cache = self._antpairpol_cache[model] @@ -186,11 +192,11 @@ def get(self, component, ant1=None, ant2=None, pol=None): use_defaults = kwargs.pop("defaults", {}) if use_defaults: self.apply_defaults(**use_defaults) - + # instantiate the model if it's a class if is_class: model = model(**kwargs) - + # if ant1, ant2 not specified, then do the whole array if ant1 is None and ant2 is None: # re-add seed_mode to the kwargs @@ -198,26 +204,26 @@ def get(self, component, ant1=None, ant2=None, pol=None): # get the data data = self._iteratively_apply( - model, add_vis=False, ret_vis=True, + model, + add_vis=False, + ret_vis=True, antpairpol_cache=antpairpol_cache, **kwargs ) - + # return a subset if a polarization is specified if pol is None: return data else: pol_ind = self.data.get_pols().index(pol) return data[:, 0, :, pol_ind] - + # seed the RNG if desired, but be careful... if seed_mode == "once": # in this case, we need to use _iteratively_apply # otherwise, the seeding will be wrong kwargs["seed_mode"] = seed_mode - data = self._iteratively_apply( - model, add_vis=False, ret_vis=True, **kwargs - ) + data = self._iteratively_apply(model, add_vis=False, ret_vis=True, **kwargs) blt_inds = self.data.antpair2ind((ant1, ant2)) if pol is None: return data[blt_inds, 0, :, :] @@ -225,13 +231,11 @@ def get(self, component, ant1=None, ant2=None, pol=None): pol_ind = self.data.get_pols().index(pol) return data[blt_inds, 0, :, pol_ind] elif seed_mode == "redundant": - if any( - [(ant2, ant1) == item for item in antpairpol_cache] - ): + if any([(ant2, ant1) == item for item in antpairpol_cache]): self._seed_rng(seed_mode, model, ant2, ant1) else: self._seed_rng(seed_mode, model, ant1, ant2) - + # get the arguments necessary for the model args = self._initialize_args_from_model(model) args = self._update_args(args, ant1, ant2, pol) @@ -245,27 +249,26 @@ def plot_array(self): """ import matplotlib.pyplot as plt - fig = plt.figure(figsize=(10,8)) - ax = fig.add_subplot(1,1,1) + + fig = plt.figure(figsize=(10, 8)) + ax = fig.add_subplot(1, 1, 1) ax.set_xlabel("East Position [m]", fontsize=12) ax.set_ylabel("North Position [m]", fontsize=12) ax.set_title("Array Layout", fontsize=12) dx = 0.25 for ant, pos in self.antpos.items(): - ax.plot(pos[0], pos[1], color='k', marker='o') + ax.plot(pos[0], pos[1], color="k", marker="o") ax.text(pos[0] + dx, pos[1] + dx, ant) return fig def refresh(self): """Refresh the Simulator object. - This zeros the data array, resets the history, and clears the + This zeros the data array, resets the history, and clears the instance's _components dictionary. """ - self.data.data_array = np.zeros( - self.data.data_array.shape, dtype=np.complex - ) - self.data.history = '' + self.data.data_array = np.zeros(self.data.data_array.shape, dtype=np.complex) + self.data.history = "" self._components.clear() self._antpairpol_cache = [] @@ -290,19 +293,17 @@ def run_sim(self, sim_file=None, **sim_params): # make sure that only sim_file or sim_params are specified if not (bool(sim_file) ^ bool(sim_params)): raise ValueError( - "Either an absolute path to a simulation configuration " - "file or a dictionary of simulation parameters may be " + "Either an absolute path to a simulation configuration " + "file or a dictionary of simulation parameters may be " "passed, but not both. Please only pass one of the two." ) # read the simulation file if provided if sim_file is not None: - with open(sim_file, 'r') as config: + with open(sim_file, "r") as config: try: - sim_params = yaml.load( - config.read(), Loader=yaml.FullLoader - ) - except: + sim_params = yaml.load(config.read(), Loader=yaml.FullLoader) + except Exception: print("The configuration file was not able to be loaded.") print("Please fix the file and try again.") sys.exit() @@ -312,46 +313,46 @@ def run_sim(self, sim_file=None, **sim_params): # make sure that the parameters are a dictionary if not isinstance(params, dict): raise TypeError( - "The parameters for {component} are not formatted " - "properly. Please ensure that the parameters for " - "each component are specified using a " + "The parameters for {component} are not formatted " + "properly. Please ensure that the parameters for " + "each component are specified using a " "dictionary.".format(component=component) ) - + # add the component to the data value = self.add(component, **params) - + # if the user wanted to return the data, then if value is not None: yield (component, value) def chunk_sim_and_save( - self, - save_dir, + self, + save_dir, ref_files=None, Nint_per_file=None, prefix=None, sky_cmp=None, state=None, - filetype='uvh5', - clobber=True + filetype="uvh5", + clobber=True, ): """ Chunk a simulation in time and write to disk. - This function is a thin wrapper around :func:`io.chunk_sim_and_save`; + This function is a thin wrapper around :func:`io.chunk_sim_and_save`; please see that function's documentation for more information. """ io.chunk_sim_and_save( - self.data, - save_dir, + self.data, + save_dir, ref_files=ref_files, Nint_per_file=Nint_per_file, prefix=prefix, sky_cmp=sky_cmp, state=state, filetype=filetype, - clobber=clobber + clobber=clobber, ) return @@ -359,21 +360,21 @@ def chunk_sim_and_save( # TODO: write a deprecated wrapper function def add_eor(self, model, **kwargs): """ - Add an EoR-like model to the visibilities. See :meth:`add` for + Add an EoR-like model to the visibilities. See :meth:`add` for more details. """ return self.add(model, **kwargs) def add_foregrounds(self, model, **kwargs): """ - Add foregrounds to the visibilities. See :meth:`add` for + Add foregrounds to the visibilities. See :meth:`add` for more details. """ return self.add(model, **kwargs) def add_noise(self, model, **kwargs): """ - Add thermal noise to the visibilities. See :meth:`add` for + Add thermal noise to the visibilities. See :meth:`add` for more details. """ return self.add(model, **kwargs) @@ -387,20 +388,20 @@ def add_rfi(self, model, **kwargs): def add_gains(self, **kwargs): """ - Apply bandpass gains to the visibilities. See :meth:`add` for + Apply bandpass gains to the visibilities. See :meth:`add` for more details. """ - return self.add('gains', **kwargs) + return self.add("gains", **kwargs) def add_sigchain_reflections(self, ants=None, **kwargs): """ - Apply reflection gains to the visibilities. See :meth:`add` for + Apply reflection gains to the visibilities. See :meth:`add` for more details. """ kwargs.update(ants=ants) - return self.add('reflections', **kwargs) + return self.add("reflections", **kwargs) - def add_xtalk(self, model='gen_whitenoise_xtalk', bls=None, **kwargs): + def add_xtalk(self, model="gen_whitenoise_xtalk", bls=None, **kwargs): """Add crosstalk to the visibilities. See :meth:`add` for more details.""" kwargs.update(vis_filter=bls) return self.add(model, **kwargs) @@ -418,15 +419,16 @@ def _apply_filter(vis_filter, ant1, ant2, pol): multikey = any(isinstance(key, (list, tuple)) for key in vis_filter) # iterate over the keys, find if any are okay if multikey: - apply_filter = [Simulator._apply_filter(key, ant1, ant2, pol) - for key in vis_filter] + apply_filter = [ + Simulator._apply_filter(key, ant1, ant2, pol) for key in vis_filter + ] # if a single filter says to let it pass, then do so return all(apply_filter) elif all(item is None for item in vis_filter): # support passing tuple of None return False elif len(vis_filter) == 1: - # check if the polarization matches, since the only + # check if the polarization matches, since the only # string identifiers should be polarization strings if isinstance(vis_filter, str): return not pol == vis_filter[0] @@ -438,15 +440,14 @@ def _apply_filter(vis_filter, ant1, ant2, pol): # an antpol is specified; a baseline is specified # first, handle the case of two polarizations if all(isinstance(key, str) for key in vis_filter): - return not pol in vis_filter + return pol not in vis_filter # otherwise it's simple else: return not all(key in (ant1, ant2, pol) for key in vis_filter) elif len(vis_filter) == 3: # assume it's a proper antpairpol return not ( - vis_filter == [ant1, ant2, pol] or - vis_filter == [ant2, ant1, pol] + vis_filter == [ant1, ant2, pol] or vis_filter == [ant2, ant1, pol] ) else: # assume it's some list of antennas/polarizations @@ -460,11 +461,11 @@ def _initialize_data(self, data, **kwargs): self.data = io.empty_uvdata(**kwargs) elif isinstance(data, str): self.data = self._read_datafile(data, **kwargs) - self.extras['data_file'] = data + self.extras["data_file"] = data elif isinstance(data, UVData): self.data = data else: - raise TypeError("Unsupported type.") # make msg better + raise TypeError("Unsupported type.") # make msg better def _initialize_args_from_model(self, model): # TODO: docstring @@ -474,9 +475,12 @@ def _initialize_args_from_model(self, model): _ = model_params.pop("kwargs", None) # pull the lst and frequency arrays as required - args = {param : getattr(self, param) for param in model_params - if param in ("lsts", "freqs")} - + args = { + param: getattr(self, param) + for param in model_params + if param in ("lsts", "freqs") + } + model_params.update(args) return model_params @@ -490,9 +494,15 @@ def _iterate_antpair_pols(self): pol_ind = self.data.get_pols().index(pol) yield ant1, ant2, pol, blt_inds, pol_ind - def _iteratively_apply(self, model, add_vis=True, ret_vis=False, - vis_filter=None, antpairpol_cache=None, - **kwargs): + def _iteratively_apply( + self, + model, + add_vis=True, + ret_vis=False, + vis_filter=None, + antpairpol_cache=None, + **kwargs + ): # TODO: docstring """ """ @@ -505,24 +515,24 @@ def _iteratively_apply(self, model, add_vis=True, ret_vis=False, "{model}".format(model=self._get_model_name(model)) ) return - + # make an empty list for antpairpol cache if it's none if antpairpol_cache is None: antpairpol_cache = [] - # pull lsts/freqs if required and find out which extra + # pull lsts/freqs if required and find out which extra # parameters are required args = self._initialize_args_from_model(model) - + # figure out whether or not to seed the RNG seed_mode = kwargs.pop("seed_mode", None) - + # get a copy of the data array data_copy = self.data.data_array.copy() - + # find out if the model is multiplicative is_multiplicative = getattr(model, "is_multiplicative", None) - + # handle user-defined functions as the passed model if is_multiplicative is None: warnings.warn( @@ -542,12 +552,12 @@ def _iteratively_apply(self, model, add_vis=True, ret_vis=False, # check if the antpolpair or its conjugate have data bl_in_cache = (ant1, ant2, pol) in antpairpol_cache conj_in_cache = (ant2, ant1, pol) in antpairpol_cache - + if seed_mode == "redundant" and conj_in_cache: seed_mode = self._seed_rng(seed_mode, model, ant2, ant1) elif seed_mode is not None: seed_mode = self._seed_rng(seed_mode, model, ant1, ant2) - + # parse the model signature to get the required arguments use_args = self._update_args(args, ant1, ant2, pol) @@ -563,10 +573,10 @@ def _iteratively_apply(self, model, add_vis=True, ret_vis=False, # get the gains for the entire array # this is sloppy, but ensures seeding works correctly gains = model(**use_args) - + # now get the product g_1g_2* gain = gains[ant1] * np.conj(gains[ant2]) - + # don't actually do anything if we're filtering this if apply_filter: gain = np.ones(gain.shape) @@ -574,22 +584,22 @@ def _iteratively_apply(self, model, add_vis=True, ret_vis=False, # apply the effect to the appropriate part of the data data_copy[blt_inds, 0, :, pol_ind] *= gain else: - # if the conjugate baseline has been simulated and - # the RNG was only seeded initially, then we should + # if the conjugate baseline has been simulated and + # the RNG was only seeded initially, then we should # not re-simulate to ensure invariance under complex # conjugation and swapping antennas if conj_in_cache and seed_mode is None: - conj_blts = sim.data.antpair2ind((ant2,ant1)) - vis = ( - data_copy - self.data.data_array - )[conj_blts, 0, :, pol_ind].conj() - else: + conj_blts = self.data.antpair2ind((ant2, ant1)) + vis = (data_copy - self.data.data_array)[ + conj_blts, 0, :, pol_ind + ].conj() + else: vis = model(**use_args) - + # filter what's actually having data simulated if apply_filter: vis = np.zeros(vis.shape, dtype=np.complex) - + # and add it in data_copy[blt_inds, 0, :, pol_ind] += vis @@ -613,7 +623,6 @@ def _iteratively_apply(self, model, add_vis=True, ret_vis=False, return data_copy else: self.data.data_array = data_copy - @staticmethod def _read_datafile(datafile, **kwargs): @@ -629,9 +638,7 @@ def _seed_rng(self, seed_mode, model, ant1=None, ant2=None): """ """ if not type(seed_mode) is str: - raise TypeError( - "The seeding mode must be specified as a string." - ) + raise TypeError("The seeding mode must be specified as a string.") if seed_mode == "redundant": if ant1 is None or ant2 is None: raise TypeError( @@ -645,22 +652,21 @@ def _seed_rng(self, seed_mode, model, ant1=None, ant2=None): # get the baseline integer for baseline (ant1, ant2) bl_int = self.data.antnums_to_baseline(ant1, ant2) # find out which redundant group the baseline is in - key = [bl_int in reds - for reds in self._get_reds()].index(True) + key = [bl_int in reds for reds in self._get_reds()].index(True) # seed the RNG accordingly np.random.seed(self._get_seed(model, key)) return "redundant" elif seed_mode == "once": - # this option seeds the RNG once per iteration of + # this option seeds the RNG once per iteration of # _iteratively_apply, using the same seed every time - # this is appropriate for antenna-based gains (where the + # this is appropriate for antenna-based gains (where the # entire gain dictionary is simulated each time), or for # something like PointSourceForeground, where objects on # the sky are being placed randomly np.random.seed(self._get_seed(model, 0)) return "once" elif seed_mode == "initial": - # this seeds the RNG once at the very beginning of + # this seeds the RNG once at the very beginning of # _iteratively_apply. this would be useful for something # like ThermalNoise np.random.seed(self._get_seed(model, -1)) @@ -673,7 +679,8 @@ def _update_args(self, args, ant1=None, ant2=None, pol=None): """ """ # helper for getting the correct parameter name - key = lambda requires : [arg for arg in args][requires.index(True)] + def key(requires): + return list(args)[requires.index(True)] # find out what needs to be added to args # for antenna-based gains @@ -689,20 +696,20 @@ def _update_args(self, args, ant1=None, ant2=None, pol=None): # check if this is an antenna-dependent quantity; should # only ever be true for gains (barring future changes) if requires_ants: - new_param = {key(_requires_ants) : self.antpos} + new_param = {key(_requires_ants): self.antpos} # check if this is something requiring a baseline vector # current assumption is that these methods require the # baseline vector to be provided in nanoseconds elif requires_bl_vec: bl_vec = self.antpos[ant1] - self.antpos[ant2] bl_vec_ns = bl_vec * 1e9 / const.c.value - new_param = {key(_requires_bl_vec) : bl_vec_ns} + new_param = {key(_requires_bl_vec): bl_vec_ns} # check if this is something that depends on another # visibility. as of now, this should only be cross coupling # crosstalk elif requires_vis: autovis = self.data.get_data(ant1, ant1, pol) - new_param = {key(_requires_vis) : autovis} + new_param = {key(_requires_vis): autovis} else: new_param = {} # update appropriately and return @@ -712,17 +719,18 @@ def _update_args(self, args, ant1=None, ant2=None, pol=None): # there should no longer be any unspecified, required parameters # so this *shouldn't* error out use_args = { - key : value for key, value in use_args.items() + key: value + for key, value in use_args.items() if not type(value) is inspect.Parameter } if any([val is inspect._empty for val in use_args.values()]): warnings.warn( - "One of the required parameters was not extracted. " \ - "Please check that the parameters for the model you " \ - "are trying to add are detectable by the Simulator. " \ - "The Simulator will automatically find the following " \ - "required parameters: \nlsts \nfreqs \nAnything that " \ + "One of the required parameters was not extracted. " + "Please check that the parameters for the model you " + "are trying to add are detectable by the Simulator. " + "The Simulator will automatically find the following " + "required parameters: \nlsts \nfreqs \nAnything that " "starts with 'ant' or 'bl'\n Anything containing 'vis'." ) @@ -737,7 +745,7 @@ def _get_model_parameters(model): # this doesn't work correctly if done on one line model_params = {} model_params.update(**call_params, **init_params) - _ = model_params.pop("kwargs", None) + _ = model_params.pop("kwargs", None) return model_params @staticmethod @@ -759,7 +767,7 @@ def _get_component(component): "function to a callable class and try again." ) if callable(component): - # if it's callable, then it's either a user-defined + # if it's callable, then it's either a user-defined # function or a class instance return component, False else: @@ -767,17 +775,17 @@ def _get_component(component): # TODO: update this error message to reflect the # change in allowed component types raise TypeError( - "``component`` must be either a class which " - "derives from ``SimulationComponent`` or an " - "instance of a callable class, or a function, " - "whose signature is:\n" - "func(lsts, freqs, *args, **kwargs)\n" - "If it is none of the above, then it must be " - "a string which corresponds to the name of a " + "``component`` must be either a class which " + "derives from ``SimulationComponent`` or an " + "instance of a callable class, or a function, " + "whose signature is:\n" + "func(lsts, freqs, *args, **kwargs)\n" + "If it is none of the above, then it must be " + "a string which corresponds to the name of a " "``hera_sim`` class or an alias thereof." ) - - # keep track of all known aliases in case desired + + # keep track of all known aliases in case desired # component isn't found in the search all_aliases = [] for registry in SimulationComponent.__subclasses__(): @@ -789,7 +797,7 @@ def _get_component(component): all_aliases.append(alias) if component.lower() in aliases: return model, True - + # if this part is executed, then the model wasn't found, so msg = "The component '{component}' wasn't found. The " msg += "following aliases are known: \n" @@ -808,7 +816,7 @@ def _generate_seed(self, model, key): np.random.seed(int(time.time())) if model not in self._seeds: self._seeds[model] = {} - self._seeds[model][key] = np.random.randint(2**32) + self._seeds[model][key] = np.random.randint(2 ** 32) def _generate_redundant_seeds(self, model): # TODO: docstring @@ -820,12 +828,6 @@ def _generate_redundant_seeds(self, model): for j in range(len(self._get_reds())): self._generate_seed(model, j) - def _get_reds(self): - # TODO: docstring - """ - """ - return self.data.get_redundancies()[0] - def _get_seed(self, model, key): # TODO: docstring """ @@ -836,7 +838,7 @@ def _get_seed(self, model, key): if key not in self._seeds[model]: self._generate_seed(model, key) return self._seeds[model][key] - + @staticmethod def _get_model_name(model): # TODO: docstring @@ -871,17 +873,24 @@ def _sanity_check(self, model): """ has_data = not np.all(self.data.data_array == 0) is_multiplicative = getattr(model, "is_multiplicative", False) - contains_multiplicative_effect = any([ + contains_multiplicative_effect = any( + [ self._get_component(component)[0].is_multiplicative - for component in self._components]) + for component in self._components + ] + ) if is_multiplicative and not has_data: - warnings.warn("You are trying to compute a multiplicative " - "effect, but no visibilities have been " - "simulated yet.") + warnings.warn( + "You are trying to compute a multiplicative " + "effect, but no visibilities have been " + "simulated yet." + ) elif not is_multiplicative and contains_multiplicative_effect: - warnings.warn("You are adding visibilities to a data array " - "*after* multiplicative effects have been " - "introduced.") + warnings.warn( + "You are adding visibilities to a data array " + "*after* multiplicative effects have been " + "introduced." + ) def _update_history(self, model, **kwargs): # TODO: docstring @@ -893,6 +902,5 @@ def _update_history(self, model, **kwargs): kwargs["defaults"] = defaults._config_name for param, value in kwargs.items(): msg += "{param} = {value}\n".format(param=param, value=value) - msg = msg.format(version=version, component=model) + msg = msg.format(version=__version__, component=model) self.data.history += msg - diff --git a/hera_sim/version.py b/hera_sim/version.py deleted file mode 100644 index 0c92f2fd..00000000 --- a/hera_sim/version.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -import subprocess -import json - - -def construct_version_info(): - hera_sim_dir = os.path.dirname(os.path.realpath(__file__)) - version_file = os.path.join(hera_sim_dir, "VERSION") - version = open(version_file).read().strip() - - try: - git_origin = subprocess.check_output( - ["git", "-C", hera_sim_dir, "config", "--get", "remote.origin.url"], - stderr=subprocess.STDOUT, - ).strip() - git_hash = subprocess.check_output( - ["git", "-C", hera_sim_dir, "rev-parse", "HEAD"], stderr=subprocess.STDOUT - ).strip() - git_description = subprocess.check_output( - ["git", "-C", hera_sim_dir, "describe", "--dirty", "--tag", "--always"] - ).strip() - git_branch = subprocess.check_output( - ["git", "-C", hera_sim_dir, "rev-parse", "--abbrev-ref", "HEAD"], - stderr=subprocess.STDOUT, - ).strip() - git_version = subprocess.check_output( - ["git", "-C", hera_sim_dir, "describe", "--always", "--tags", "--abbrev=0"] - ).strip() - except: # pragma: no cover - can't figure out how to test exception. - try: - # Check if a GIT_INFO file was created when installing package - git_file = os.path.join(hera_sim_dir, "GIT_INFO") - with open(git_file) as data_file: - data = [x.encode("UTF8") for x in json.loads(data_file.read().strip())] - git_origin = data[0] - git_hash = data[1] - git_description = data[2] - git_branch = data[3] - except: - git_origin = "" - git_hash = "" - git_description = "" - git_branch = "" - - version_info = { - "version": version, - "git_origin": git_origin, - "git_hash": git_hash, - "git_description": git_description, - "git_branch": git_branch, - } - return version_info - - -version_info = construct_version_info() -version = version_info["version"] -git_origin = version_info["git_origin"] -git_hash = version_info["git_hash"] -git_description = version_info["git_description"] -git_branch = version_info["git_branch"] - -# String to add to history of any files written with this version of hera_sim -hera_sim_version_str = "hera_sim version: " + version + "." -if git_hash is not "": - hera_sim_version_str += ( - " Git origin: " - + str(git_origin) - + ". Git hash: " - + str(git_hash) - + ". Git branch: " - + str(git_branch) - + ". Git description: " - + str(git_description) - + "." - ) - - -def main(): - print(("Version = {0}".format(version))) - print(("git origin = {0}".format(git_origin))) - print(("git branch = {0}".format(git_branch))) - print(("git description = {0}".format(git_description))) - - -if __name__ == "__main__": - main() diff --git a/pyproject.toml b/pyproject.toml index b04ca26c..98cd2aeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [build-system] -requires = ["setuptools>=30.3.0", "wheel", 'numpy>=1.14', 'pip', 'future'] +requires = ["setuptools>=30.3.0", "wheel", 'setuptools_scm'] +build-backend = "setuptools.build_meta" [tool.black] line-length = 88 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..ae73f3d8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,108 @@ +# This file is used to configure your project. +# Read more about the various options under: +# http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files + +[metadata] +name = hera_sim +description = A collection of simulation routines describing the HERA instrument. +author = HERA Team +author_email = steven.g.murray@asu.edu +license = BSD +long_description = file: README.md +long_description_content_type = text/x-rst; charset=UTF-8 +url = https://github.com/HERA-Team/hera_sim +project_urls = + Documentation = https://hera_sim.readthedocs.org +# Change if running only on Windows, Mac or Linux (comma-separated) +platforms = any +# Add here all kinds of additional classifiers as defined under +# https://pypi.python.org/pypi?%3Aaction=list_classifiers +classifiers = + Development Status :: 4 - Beta + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Intended Audience :: Science/Research + License :: OSI Approved + Natural Language :: English + Topic :: Scientific/Engineering :: Physics + Topic :: Scientific/Engineering :: Astronomy + +[options] +zip_safe = False +packages = find: +include_package_data = True +package_dir = + =. +# Add here dependencies of your project (semicolon/line-separated), e.g. +install_requires = + numpy>=1.14 + scipy + cached_property + pyuvsim + pyuvdata>=2.0<3.0 + aipy>=3.0<4 + click + +[options.packages.find] +exclude = + tests + +[options.extras_require] +# Add here additional requirements for extra features, to install with: +# `pip install cal_coefficients[PDF]` like: +# PDF = ReportLab; RXP +# Add here test requirements (semicolon/line-separated) +bda = + bda @ git+git://github.com/HERA-Team/baseline_dependent_averaging +gpu = + hera_gpu @ git+git://github.com/hera-team/hera_gpu +docs = + sphinx>=1.8 + nbsphinx + ipython + sphinx_autorun + numpydoc>=0.8 + nbsphinx +tests = + coverage>=4.5.1 + pytest>=3.5.1 + pytest-cov>=2.5.1 + pre-commit +dev = + Sphinx>=1.8 + numpydoc>=0.8.0 + nbsphinx + coverage>=4.5.1 + pytest>=3.5.1 + pytest-cov>=2.5.1 + pre-commit + +[options.entry_points] +console_scripts = + hera_sim = hera_sim.cli:main + +[tool:pytest] +# Options for py.test: +# Specify command line options as you would do when invoking py.test directly. +# e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml +# in order to write a coverage file that can be read by Jenkins. +addopts = + --cov hera_sim --cov-report term-missing + --verbose +norecursedirs = + dist + build + .tox +testpaths = tests + +[aliases] +dists = bdist_wheel + +[bdist_wheel] +# Use this option if your package is pure-python +universal = 1 + +[build_sphinx] +source_dir = docs +build_dir = build/sphinx diff --git a/setup.py b/setup.py index a3cce206..60684932 100644 --- a/setup.py +++ b/setup.py @@ -1,75 +1,3 @@ -from setuptools import setup, find_packages -import glob -import os -import sys +from setuptools import setup -# Import version this way so as not to import the entire hera_sim package. -# Importing all of hera_sim creates problems when requirements aren't installed. -sys.path.append("hera_sim") -import version - -import json - -data = [ - str(version.git_origin), - str(version.git_hash), - str(version.git_description), - str(version.git_branch), -] - -if sys.version[0] == '2': - with open(os.path.join("hera_sim", "GIT_INFO"), "w") as outfile: - json.dump(data, outfile) -else: - with open(os.path.join("hera_sim", "GIT_INFO"), "w", encoding='utf8') as outfile: - json.dump(data, outfile) - - -def package_files(package_dir, subdirectory): - # walk the input package_dir/subdirectory - # return a package_data list - paths = [] - directory = os.path.join(package_dir, subdirectory) - for (path, directories, filenames) in os.walk(directory): - for filename in filenames: - path = path.replace(package_dir + "/", "") - paths.append(os.path.join(path, filename)) - return paths - - -data_files = package_files("hera_sim", "data") -data_files += package_files("hera_sim", "config") - -setup_args = { - "name": "hera_sim", - "author": "HERA Team", - "url": "https://github.com/HERA-Team/hera_sim", - "license": "BSD", - "description": "collection of simulation routines describing the HERA instrument.", - "package_dir": {"hera_sim": "hera_sim"}, - "packages": find_packages(), - "include_package_data": True, - "install_requires": [ - 'numpy>=1.14', - 'scipy', - 'cached_property', - 'pyuvsim', - 'pyuvdata', - 'aipy>=3.0', - 'click', - 'astropy-healpix', # pyuvsim depenency not automatically installed, - "future" - ], - "extras_require": { - "bda": ["bda @ git+git://github.com/HERA-Team/baseline_dependent_averaging"], - 'gpu': ['hera_gpu @ git+git://github.com/hera-team/hera_gpu'], - }, - "version": version.version, - "package_data": {"hera_sim": data_files}, - "zip_safe": False, - "entry_points": {"console_scripts": ["hera_sim = hera_sim.cli:main"]}, -} - - -if __name__ == "__main__": - setup(*(), **setup_args) +setup()