Repository: RadioAstronomySoftwareGroup/pyuvsim

Issue:
Paper reference for pyuvsim
I think the JOSS paper reference should be mentioned on the pyuvsim Github and documentation page.  

Comments:

Patch:
diff --git a/README.md b/README.md
index 530118e..c5f7d9f 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ precision necessary for 21cm cosmology science,
 1. High level of test coverage including accuracy (design goal is 97%).
 2. Testing against analytic calculations, monitored by continuous integration (see memo #XXX)
 3. Comparison with external simulations with standardized reference simulations
+
 ## Usability and extensibility
 A secondary goal is a community simulation environment which provides well documented and flexible code to support a diversity of use cases.
 Key elements of this approach include:
@@ -41,6 +42,14 @@ Each addition of new physics is validated against analytic calculations and incl
 1. Time domain sources (TODO)
 1. Ionospheric scintillation (TODO)
 
+# Citation
+Please cite pyuvsim by citing our JOSS paper:
+
+Lanman et al., (2019). pyuvsim: A comprehensive simulation package for radio
+interferometers in python. Journal of Open Source Software, 4(37), 1234,
+https://doi.org/10.21105/joss.01234
+
+[ADS Link](https://ui.adsabs.harvard.edu/abs/2019JOSS....4.1234L/abstract)
 
 
 ## Installation
@@ -174,9 +183,14 @@ We use a `generation.major.minor` format.
 Testing: Backed by unittests, internal model validation, and significant external comparison.
 * Major - Adds new physical effect or major computational improvement. Small number of improvements with each release.
 Testing: Backed by unittests, internal model validation and limited external comparison.
-* Minor - Bug fixes and small improvements not expected to change physical model.
+* Minor - Bug fixes and small improvements not expected to change physical model
+and which do not include breaking API changes.
 Testing: Backed by unittests
 
+We do our best to provide a significant period (usually 2 major generations) of
+deprecation warnings for all breaking changes to the API.
+We track all changes in our [changelog](https://github.com/RadioAstronomySoftwareGroup/pyuvsim/blob/main/CHANGELOG.md).
+
 ### Some helpful definitions
 * __Physical effects__: things like polarization effects, noise, ionospheric modeling, or nonterrestrial observing positions.
 * __Major computational improvement__:  Support for new catalog types (e.g, diffuse maps), new analysis tools, changes to parallelization scheme
diff --git a/ci/min_deps.yaml b/ci/min_deps.yaml
index 87e1c5b..60d89a1 100644
--- a/ci/min_deps.yaml
+++ b/ci/min_deps.yaml
@@ -10,6 +10,7 @@ dependencies:
   - pip
   - pytest
   - pytest-cov
+  - pytest-xdist
   - pyuvdata>=2.2.10
   - pyyaml>=5.1
   - scipy>=1.3
diff --git a/ci/min_versions.yaml b/ci/min_versions.yaml
index 13f2305..8b4c7ed 100644
--- a/ci/min_versions.yaml
+++ b/ci/min_versions.yaml
@@ -16,6 +16,7 @@ dependencies:
   - python-casacore==3.3.1
   - pytest
   - pytest-cov
+  - pytest-xdist
   - pyuvdata==2.2.10
   - pyyaml==5.1.*
   - scipy==1.3.*
diff --git a/pyuvsim/simsetup.py b/pyuvsim/simsetup.py
index 8dfb2b1..c7dbf44 100644
--- a/pyuvsim/simsetup.py
+++ b/pyuvsim/simsetup.py
@@ -2028,13 +2028,13 @@ def initialize_uvdata_from_keywords(
         # Increment name appropriately:
         output_layout_filepath = os.path.join(path_out, output_layout_filename)
         output_layout_filename = os.path.basename(
-            check_file_exists_and_increment(output_layout_filepath, 'csv')
+            check_file_exists_and_increment(output_layout_filepath)
         )
 
         if output_yaml_filename is None:
             output_yaml_filename = 'obsparam.yaml'
         output_yaml_filename = check_file_exists_and_increment(
-            os.path.join(path_out, output_yaml_filename), 'yaml'
+            os.path.join(path_out, output_yaml_filename)
         )
 
         if antenna_layout_filepath is not None:
diff --git a/pyuvsim/utils.py b/pyuvsim/utils.py
index d3bc5a2..654d677 100644
--- a/pyuvsim/utils.py
+++ b/pyuvsim/utils.py
@@ -146,43 +146,15 @@ def zenithangle_azimuth_to_altaz(zenith_angle, azimuth):
     return altitude, new_azimuth
 
 
-def strip_extension(filepath, ext=None):
-    """
-    Remove extension from file.
-
-    Parameters
-    ----------
-    ext : str
-        Extenstion to remove. If not specified, only 'uvfits', 'uvh5', 'yaml' extensions
-        are removed.
-    """
-    if '.' not in filepath:
-        return filepath, ''
-    file_list = filepath.split('.')
-    if ext is not None:
-        return filepath[:-len(ext) - 1], '.' + ext
-    ext = file_list[-1]
-    # miriad files might not have an extension
-    # limited list of recognized extensions
-    if ext not in ['uvfits', 'uvh5', 'yaml']:
-        return filepath, ''
-    return ".".join(file_list[:-1]), '.' + file_list[-1]
-
-
-def check_file_exists_and_increment(filepath, extension=None):
+def check_file_exists_and_increment(filepath):
     """
     Check for a file and increment the name if it does to ensure a unique name.
 
     Given filepath (path + filename), check if it exists. If so, add a _1
     at the end, if that exists add a _2, and so on.
 
-    Parameters
-    ----------
-    extension : str
-        File extension, to be removed before modifying the filename and then added back.
-
     """
-    base_filepath, ext = strip_extension(filepath, extension)
+    base_filepath, ext = os.path.splitext(filepath)
     bf_list = base_filepath.split('_')
     if bf_list[-1].isdigit():
         base_filepath = '_'.join(bf_list[:-1])


Test patch:
diff --git a/.github/workflows/testsuite.yaml b/.github/workflows/testsuite.yaml
index 177c297..5b53ac8 100644
--- a/.github/workflows/testsuite.yaml
+++ b/.github/workflows/testsuite.yaml
@@ -52,7 +52,7 @@ jobs:
 
       - name: Run Tests
         run: |
-          python -m pytest --cov=pyuvsim --cov-config=.coveragerc --cov-report xml:./coverage.xml --junitxml=test-reports/xunit.xml
+          python -m pytest -n auto --cov=pyuvsim --cov-config=.coveragerc --cov-report xml:./coverage.xml --junitxml=test-reports/xunit.xml
 
       - name: Upload Coverage
         uses: codecov/codecov-action@v3
@@ -102,7 +102,7 @@ jobs:
 
       - name: Run Tests
         run: |
-          python -m pytest --cov=pyuvsim --cov-config=.coveragerc --cov-report xml:./coverage.xml --junitxml=test-reports/xunit.xml
+          python -m pytest -n auto --cov=pyuvsim --cov-config=.coveragerc --cov-report xml:./coverage.xml --junitxml=test-reports/xunit.xml
 
       - uses: codecov/codecov-action@v3
         if: success()
@@ -151,7 +151,7 @@ jobs:
 
       - name: Run Tests
         run: |
-          python -m pytest --cov=pyuvsim --cov-config=.coveragerc --cov-report xml:./coverage.xml --junitxml=test-reports/xunit.xml
+          python -m pytest -n auto --cov=pyuvsim --cov-config=.coveragerc --cov-report xml:./coverage.xml --junitxml=test-reports/xunit.xml
 
       - uses: codecov/codecov-action@v3
         if: success()
@@ -200,7 +200,7 @@ jobs:
           pip install --no-deps .
 
       - name: Run Tests
-        run: |
+        run: |  # currently cannot use `-n auto` here because pytest-cov causes warnings when used with pytest-xdist
           python -m pytest -W error --cov=pyuvsim --cov-config=.coveragerc --cov-report xml:./coverage.xml --junitxml=test-reports/xunit.xml
 
       - name: Upload Coverage
diff --git a/ci/pyuvsim_tests.yaml b/ci/pyuvsim_tests.yaml
index 21413fb..dffed84 100644
--- a/ci/pyuvsim_tests.yaml
+++ b/ci/pyuvsim_tests.yaml
@@ -14,6 +14,7 @@ dependencies:
   - python-casacore>=3.3
   - pytest
   - pytest-cov
+  - pytest-xdist
   - pyuvdata>=2.2.10
   - pyyaml>=5.1
   - scipy>=1.3
diff --git a/pyuvsim/tests/test_run.py b/pyuvsim/tests/test_run.py
index bc9e912..0c6bbb2 100644
--- a/pyuvsim/tests/test_run.py
+++ b/pyuvsim/tests/test_run.py
@@ -113,8 +113,14 @@ def test_run_paramfile_uvsim(goto_tempdir, paramfile):
 @pytest.mark.filterwarnings("ignore:Input ra and dec parameters are being used instead")
 @pytest.mark.filterwarnings("ignore:Cannot check consistency of a string-mode BeamList")
 @pytest.mark.filterwarnings("ignore:invalid value encountered in divide")
-@pytest.mark.parametrize('model', ['monopole', 'cosza', 'quaddome', 'monopole-nonflat'])
-def test_analytic_diffuse(model, tmpdir):
+# Set the tolerances as low as we can achieve currently. Ideally these tolerances
+# would be lower, but it's complicated.
+# See Lanman, Murray and Jacobs, 2022, DOI: 10.3847/1538-4365/ac45fd
+@pytest.mark.parametrize(
+    ('model', 'tol'),
+    [('monopole', 3e-4), ('cosza', 2e-4), ('quaddome', 8e-5), ('monopole-nonflat', 3e-4)],
+)
+def test_analytic_diffuse(model, tol, tmpdir):
     # Generate the given model and simulate for a few baselines.
     # Import from analytic_diffuse  (consider moving to rasg_affiliates?)
     pytest.importorskip('analytic_diffuse')
@@ -130,7 +136,7 @@ def test_analytic_diffuse(model, tmpdir):
     elif model == 'monopole-nonflat':
         modname = 'monopole'
         use_w = True
-        params['order'] = 30    # Expansion order for the non-flat monopole solution.
+        params['order'] = 50    # Expansion order for the non-flat monopole solution.
 
     # Making configuration files for this simulation.
     template_path = os.path.join(SIM_DATA_PATH, 'test_config', 'obsparam_diffuse_sky.yaml')
@@ -160,6 +166,9 @@ def test_analytic_diffuse(model, tmpdir):
     obspar['telescope']['telescope_config_name'] = herauniform_path
     obspar['sources']['diffuse_model'] = modname
     obspar['sources'].update(params)
+    if model == "monopole":
+        # use a higher nside for monopole to improve the accuracy
+        obspar['sources']["map_nside"] = 256
     obspar['filing']['outfile_name'] = 'diffuse_sim.uvh5'
     obspar['filing']['output_format'] = 'uvh5'
     obspar['filing']['outdir'] = str(tmpdir)
@@ -174,7 +183,7 @@ def test_analytic_diffuse(model, tmpdir):
     soln = analytic_diffuse.get_solution(modname)
     uvw_lam = uv_out.uvw_array * uv_out.freq_array[0] / c_ms
     ana = soln(uvw_lam, **params)
-    assert np.allclose(ana / 2, dat, atol=1e-2)
+    np.testing.assert_allclose(ana / 2, dat, atol=tol, rtol=0)
 
 
 @pytest.mark.filterwarnings("ignore:Cannot check consistency of a string-mode BeamList")
diff --git a/pyuvsim/tests/test_utils.py b/pyuvsim/tests/test_utils.py
index dd843de..dbd6320 100644
--- a/pyuvsim/tests/test_utils.py
+++ b/pyuvsim/tests/test_utils.py
@@ -103,10 +103,7 @@ def test_file_namer(tmpdir, ext):
             f.write(' ')
         fnames.append(fname)
     existing_file = fnames[0]
-    if ext == '.ext':
-        new_filepath = simutils.check_file_exists_and_increment(existing_file, 'ext')
-    else:
-        new_filepath = simutils.check_file_exists_and_increment(existing_file)
+    new_filepath = simutils.check_file_exists_and_increment(existing_file)
     assert new_filepath.endswith(f"_111{ext}")