Repository: PSLmodels/OG-Core (language: python)

Issue:
Test failures in Python 3.12.0 on Mac M1
I recently rebuilt the `ogcore-dev` environment and tried to run the OG-Core testing suite. It resulted in an error when collecting tests:

```
(ogcore-dev) jason.debacker@JDEBACKER-7 OG-Core % pytest
============================= test session starts ==============================
platform darwin -- Python 3.12.0, pytest-7.4.2, pluggy-1.3.0
rootdir: /Users/jason.debacker/repos/OG-Core
configfile: pytest.ini
testpaths: ./tests
plugins: xdist-3.3.1
collecting 239 items                                                           Fatal Python error: Illegal instruction

Current thread 0x0000000200983240 (most recent call first):
  File "/Users/jason.debacker/anaconda3/envs/ogzaf-dev/lib/python3.11/site-packages/paramtools/sorted_key_list.py", line 53 in <lambda>
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/sortedcontainers/sortedlist.py", line 1878 in update
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/sortedcontainers/sortedlist.py", line 1750 in __init__
  File "/Users/jason.debacker/repos/OG-Core/ogcore/utils.py", line 393 in safe_read_pickle
  File "/Users/jason.debacker/repos/OG-Core/tests/test_output_plots.py", line 26 in <module>
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/assertion/rewrite.py", line 178 in exec_module
  File "<frozen importlib._bootstrap>", line 929 in _load_unlocked
  File "<frozen importlib._bootstrap>", line 1325 in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 1354 in _find_and_load
  File "<frozen importlib._bootstrap>", line 1381 in _gcd_import
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/importlib/__init__.py", line 90 in import_module
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/pathlib.py", line 567 in import_path
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/python.py", line 617 in _importtestmodule
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/python.py", line 528 in _getobj
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/python.py", line 310 in obj
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/python.py", line 545 in _inject_setup_module_fixture
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/python.py", line 531 in collect
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/runner.py", line 372 in <lambda>
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/runner.py", line 341 in from_call
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/runner.py", line 372 in pytest_make_collect_report
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_callers.py", line 77 in _multicall
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_manager.py", line 115 in _hookexec
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_hooks.py", line 493 in __call__
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/runner.py", line 547 in collect_one_node
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/main.py", line 842 in genitems
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/main.py", line 675 in perform_collect
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/main.py", line 335 in pytest_collection
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_callers.py", line 77 in _multicall
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_manager.py", line 115 in _hookexec
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_hooks.py", line 493 in __call__
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/main.py", line 324 in _main
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/main.py", line 271 in wrap_session
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/main.py", line 318 in pytest_cmdline_main
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_callers.py", line 77 in _multicall
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_manager.py", line 115 in _hookexec
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/pluggy/_hooks.py", line 493 in __call__
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/config/__init__.py", line 169 in main
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/lib/python3.12/site-packages/_pytest/config/__init__.py", line 192 in console_main
  File "/Users/jason.debacker/anaconda3/envs/ogcore-dev/bin/pytest", line 10 in <module>

Extension modules: yaml._yaml, cytoolz.utils, cytoolz.itertoolz, cytoolz.functoolz, cytoolz.dicttoolz, cytoolz.recipes, psutil._psutil_osx, psutil._psutil_posix, markupsafe._speedups, tornado.speedups, msgpack._cmsgpack, lz4._version, lz4.block._block, numpy.core._multiarray_umath, numpy.core._multiarray_tests, numpy.linalg._umath_linalg, numpy.fft._pocketfft_internal, numpy.random._common, numpy.random.bit_generator, numpy.random._bounded_integers, numpy.random._mt19937, numpy.random.mtrand, numpy.random._philox, numpy.random._pcg64, numpy.random._sfc64, numpy.random._generator, scipy._lib._ccallback_c, scipy.sparse._sparsetools, _csparsetools, scipy.sparse._csparsetools, scipy.sparse.linalg._isolve._iterative, scipy.linalg._fblas, scipy.linalg._flapack, scipy.linalg.cython_lapack, scipy.linalg._cythonized_array_utils, scipy.linalg._solve_toeplitz, scipy.linalg._decomp_lu_cython, scipy.linalg._matfuncs_sqrtm_triu, scipy.linalg.cython_blas, scipy.linalg._matfuncs_expm, scipy.linalg._decomp_update, scipy.linalg._flinalg, scipy.sparse.linalg._dsolve._superlu, scipy.sparse.linalg._eigen.arpack._arpack, scipy.sparse.csgraph._tools, scipy.sparse.csgraph._shortest_path, scipy.sparse.csgraph._traversal, scipy.sparse.csgraph._min_spanning_tree, scipy.sparse.csgraph._flow, scipy.sparse.csgraph._matching, scipy.sparse.csgraph._reordering, scipy.optimize._minpack2, scipy.optimize._group_columns, scipy._lib.messagestream, scipy.optimize._trlib._trlib, scipy.optimize._lbfgsb, _moduleTNC, scipy.optimize._moduleTNC, scipy.optimize._cobyla, scipy.optimize._slsqp, scipy.optimize._minpack, scipy.optimize._lsq.givens_elimination, scipy.optimize._zeros, scipy.optimize.__nnls, scipy.optimize._highs.cython.src._highs_wrapper, scipy.optimize._highs._highs_wrapper, scipy.optimize._highs.cython.src._highs_constants, scipy.optimize._highs._highs_constants, scipy.linalg._interpolative, scipy.optimize._bglu_dense, scipy.optimize._lsap, scipy.spatial._ckdtree, scipy.spatial._qhull, scipy.spatial._voronoi, scipy.spatial._distance_wrap, scipy.spatial._hausdorff, scipy.special._ufuncs_cxx, scipy.special._ufuncs, scipy.special._specfun, scipy.special._comb, scipy.special._ellip_harm_2, scipy.spatial.transform._rotation, scipy.optimize._direct, _brotli, pyarrow.lib, pyarrow._hdfsio, pandas._libs.tslibs.np_datetime, pandas._libs.tslibs.dtypes, pandas._libs.tslibs.base, pandas._libs.tslibs.nattype, pandas._libs.tslibs.timezones, pandas._libs.tslibs.ccalendar, pandas._libs.tslibs.fields, pandas._libs.tslibs.timedeltas, pandas._libs.tslibs.tzconversion, pandas._libs.tslibs.timestamps, pandas._libs.properties, pandas._libs.tslibs.offsets, pandas._libs.tslibs.strptime, pandas._libs.tslibs.parsing, pandas._libs.tslibs.conversion, pandas._libs.tslibs.period, pandas._libs.tslibs.vectorized, pandas._libs.ops_dispatch, pandas._libs.missing, pandas._libs.hashtable, pandas._libs.algos, pandas._libs.interval, pandas._libs.lib, pandas._libs.ops, pyarrow._compute, pandas._libs.arrays, pandas._libs.tslib, pandas._libs.sparse, pandas._libs.indexing, pandas._libs.index, pandas._libs.internals, pandas._libs.join, pandas._libs.writers, pandas._libs.window.aggregations, pandas._libs.window.indexers, pandas._libs.reshape, pandas._libs.groupby, pandas._libs.json, pandas._libs.parsers, pandas._libs.testing, scipy.interpolate._fitpack, scipy.interpolate.dfitpack, scipy.interpolate._bspl, scipy.interpolate._ppoly, scipy.interpolate.interpnd, scipy.interpolate._rbfinterp_pythran, scipy.interpolate._rgi_cython, matplotlib._c_internal_utils, PIL._imaging, matplotlib._path, kiwisolver._cext, matplotlib._image, scipy.ndimage._nd_image, _ni_label, scipy.ndimage._ni_label, scipy.integrate._odepack, scipy.integrate._quadpack, scipy.integrate._vode, scipy.integrate._dop, scipy.integrate._lsoda, scipy.special.cython_special, scipy.stats._stats, scipy.stats.beta_ufunc, scipy.stats._boost.beta_ufunc, scipy.stats.binom_ufunc, scipy.stats._boost.binom_ufunc, scipy.stats.nbinom_ufunc, scipy.stats._boost.nbinom_ufunc, scipy.stats.hypergeom_ufunc, scipy.stats._boost.hypergeom_ufunc, scipy.stats.ncf_ufunc, scipy.stats._boost.ncf_ufunc, scipy.stats.ncx2_ufunc, scipy.stats._boost.ncx2_ufunc, scipy.stats.nct_ufunc, scipy.stats._boost.nct_ufunc, scipy.stats.skewnorm_ufunc, scipy.stats._boost.skewnorm_ufunc, scipy.stats.invgauss_ufunc, scipy.stats._boost.invgauss_ufunc, scipy.stats._biasedurn, scipy.stats._levy_stable.levyst, scipy.stats._stats_pythran, scipy._lib._uarray._uarray, scipy.stats._statlib, scipy.stats._sobol, scipy.stats._qmc_cy, scipy.stats._mvn, scipy.stats._rcont.rcont, lz4.frame._frame (total: 176)
zsh: illegal hardware instruction  pytest
```

I've not seen this before an a quick search didn't reveal an obvious solution.


Discussion / hints:
Follow up.  I've been able to run `pytest tests/test_module_name.py` for most modules.  The only one I've noted giving an error like the above is `test_parameter_plots.py`.  So perhaps issues with with M1, 3.12, and a package called there?
Following up on this issue... it appears the issue is with reading files that were saved via CloudPickle in versions of Python < 3.12.

I have no problems running the model generally, but when running tests, those test modules that import objects saved via CloudPickle (e.g., `Specifications` objects or tax functions of the `mono` form) such as `test_parameter_plots.py` and `test_ouptut_tables.py`, I get a failure:

```
(ogcore-dev) jason.debacker@JDEBACKER-8 OG-Core % pytest tests/test_parameter_plots.py
============================= test session starts ==============================
platform darwin -- Python 3.12.3, pytest-8.2.2, pluggy-1.5.0
rootdir: /Users/jason.debacker/repos/OG-Core
configfile: pytest.ini
plugins: cov-5.0.0, anyio-4.4.0, xdist-3.6.1
collecting ... zsh: trace trap  pytest tests/test_parameter_plots.py
```

The lack of any traceback makes it not clear, but I if I comment out the lines reading in these CloudPickled files, I do get some tests collected and run (of course there is an error noting that I don't the the parameters object available since I didn't import it, but we can at least see that tests try to run):
```
(ogcore-dev) jason.debacker@JDEBACKER-8 OG-Core % pytest tests/test_parameter_plots.py
============================= test session starts ==============================
platform darwin -- Python 3.12.3, pytest-8.2.2, pluggy-1.5.0
rootdir: /Users/jason.debacker/repos/OG-Core
configfile: pytest.ini
plugins: cov-5.0.0, anyio-4.4.0, xdist-3.6.1
collected 1 item

tests/test_parameter_plots.py F                                          [100%]

=================================== FAILURES ===================================
_____________________________ test_plot_imm_rates ______________________________

    def test_plot_imm_rates():
        fig = parameter_plots.plot_imm_rates(
>           base_params.imm_rates,
            base_params.start_year,
            [base_params.start_year],
            include_title=True,
        )
E       NameError: name 'base_params' is not defined

tests/test_parameter_plots.py:46: NameError
=========================== short test summary info ============================
FAILED tests/test_parameter_plots.py::test_plot_imm_rates - NameError: name 'base_params' is not defined
============================== 1 failed in 1.83s ===============================
```
We already have separate parameter files for Python 3.11 and Python 3.9/10:
```python
if sys.version_info[1] < 11:
    base_params = utils.safe_read_pickle(
        os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl")
    )
else:
    base_params = utils.safe_read_pickle(
        os.path.join(
            CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl"
        )
    )
```

Other times, we don't run certain parameterizations under a different Python version:
```python
if sys.version_info[1] < 11:
    test_list = [
        (base_taxfunctions, 43, "DEP", "etr", True, None, None),
        (base_taxfunctions, 43, "DEP", "etr", False, None, "Test title"),
        (GS_nonage_spec_taxfunctions, None, "GS", "etr", True, None, None),
        (base_taxfunctions, 43, "DEP", "etr", True, [micro_data], None),
        (base_taxfunctions, 43, "DEP", "mtry", True, [micro_data], None),
        (base_taxfunctions, 43, "DEP", "mtrx", True, [micro_data], None),
        (mono_nonage_spec_taxfunctions, None, "mono", "etr", True, None, None),
    ]
    id_list = [
        "over_labinc=True",
        "over_labinc=False",
        "Non age-specific",
        "with data",
        "MTR capital income",
        "MTR labor income",
        "Mono functions",
    ]
else:
    test_list = [
        (base_taxfunctions, 43, "DEP", "etr", True, None, None),
        (base_taxfunctions, 43, "DEP", "etr", False, None, "Test title"),
        (GS_nonage_spec_taxfunctions, None, "GS", "etr", True, None, None),
        (base_taxfunctions, 43, "DEP", "etr", True, [micro_data], None),
        (base_taxfunctions, 43, "DEP", "mtry", True, [micro_data], None),
        (base_taxfunctions, 43, "DEP", "mtrx", True, [micro_data], None),
    ]
    id_list = [
        "over_labinc=True",
        "over_labinc=False",
        "Non age-specific",
        "with data",
        "MTR capital income",
        "MTR labor income",
    ]


@pytest.mark.parametrize(
    "tax_funcs,age,tax_func_type,rate_type,over_labinc,data,title",
    test_list,
    ids=id_list,
)
def test_plot_2D_taxfunc(
    tax_funcs, age, tax_func_type, rate_type, over_labinc, data, title
):
    """
    Test of plot_2D_taxfunc
    """
    if sys.version_info[1] < 11:
        fig = parameter_plots.plot_2D_taxfunc(
            2030,
            2021,
            [tax_funcs],
            age=age,
            tax_func_type=[tax_func_type],
            rate_type=rate_type,
            over_labinc=over_labinc,
            data_list=data,
            title=title,
        )

        assert fig
    else:
        assert True
```

@rickecon What do you want to do for Python 3.12?  Create a third `pkl` file for parameters?  Skip tests that require these Cloudpickled objects on 3.12?  Note we may have to update this with each Python version we want to test against because CloudPickle is not really suited for long term storage due to changes in Python.

Fix patch:
diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml
index 56889fde8..aa35afc26 100644
--- a/.github/workflows/deploy_docs.yml
+++ b/.github/workflows/deploy_docs.yml
@@ -27,7 +27,7 @@ jobs:
           miniforge-variant: Mambaforge
           activate-environment: ogcore-dev
           environment-file: environment.yml
-          python-version: "3.11"
+          python-version: "3.12"
           auto-activate-base: false
 
       - name: Build # Build Jupyter Book
diff --git a/.github/workflows/docs_check.yml b/.github/workflows/docs_check.yml
index 4e0ce22c5..35714b60b 100644
--- a/.github/workflows/docs_check.yml
+++ b/.github/workflows/docs_check.yml
@@ -25,7 +25,7 @@ jobs:
           miniforge-variant: Mambaforge
           activate-environment: ogcore-dev
           environment-file: environment.yml
-          python-version: "3.11"
+          python-version: "3.12"
           auto-activate-base: false
 
       - name: Build # Build Jupyter Book
diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml
index 8f54f3093..ea1acb773 100644
--- a/.github/workflows/publish_to_pypi.yml
+++ b/.github/workflows/publish_to_pypi.yml
@@ -16,7 +16,7 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v5
         with:
-          python-version: "3.11"
+          python-version: "3.12"
       - name: Build package
         run: make pip-package
       - name: Publish a Python distribution to PyPI
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c328e72f..ccfcf653d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.12.0] - 2024-08-20 12:00:00
+
+### Added
+
+- Support for Python 3.12
+
 ## [0.11.17] - 2024-08-18 12:00:00
 
 ### Added
@@ -302,6 +308,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Version [0.7.0] on August 30, 2021 was the first time that the OG-USA repository was detached from all of the core model logic, which was named OG-Core. Before this version, OG-USA was part of what is now the [`OG-Core`](https://github.com/PSLmodels/OG-Core) repository. In the next version of OG-USA, we adjusted the version numbering to begin with 0.1.0. This initial version of 0.7.0, was sequential from what OG-USA used to be when the OG-Core project was called OG-USA.
 - Any earlier versions of OG-USA can be found in the [`OG-Core`](https://github.com/PSLmodels/OG-Core) repository [release history](https://github.com/PSLmodels/OG-Core/releases) from [v.0.6.4](https://github.com/PSLmodels/OG-Core/releases/tag/v0.6.4) (Jul. 20, 2021) or earlier.
 
+[0.12.0]: https://github.com/PSLmodels/OG-Core/compare/v0.11.17...v0.12.0
 [0.11.17]: https://github.com/PSLmodels/OG-Core/compare/v0.11.16...v0.11.17
 [0.11.16]: https://github.com/PSLmodels/OG-Core/compare/v0.11.15...v0.11.16
 [0.11.15]: https://github.com/PSLmodels/OG-Core/compare/v0.11.14...v0.11.15
diff --git a/environment.yml b/environment.yml
index a33d4f754..68fadd400 100644
--- a/environment.yml
+++ b/environment.yml
@@ -2,7 +2,7 @@ name: ogcore-dev
 channels:
 - conda-forge
 dependencies:
-- python>=3.7.7, <3.12
+- python>=3.7.7, <3.13
 - numpy
 - ipython
 - setuptools
diff --git a/ogcore/__init__.py b/ogcore/__init__.py
index f8c550e99..a11acad60 100644
--- a/ogcore/__init__.py
+++ b/ogcore/__init__.py
@@ -20,4 +20,4 @@
 from ogcore.txfunc import *
 from ogcore.utils import *
 
-__version__ = "0.11.17"
+__version__ = "0.12.0"
diff --git a/setup.py b/setup.py
index b84cfb189..47bd7f8d8 100755
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
 
 setuptools.setup(
     name="ogcore",
-    version="0.11.17",
+    version="0.12.0",
     author="Jason DeBacker and Richard W. Evans",
     license="CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
     description="A general equilibrium overlapping generations model for fiscal policy analysis",
@@ -21,7 +21,7 @@
         "ogcore": ["default_parameters.json", "OGcorePlots.mplstyle"]
     },
     include_packages=True,
-    python_requires=">=3.7.7, <3.12",
+    python_requires=">=3.7.7, <3.13",
     install_requires=[
         "numpy",
         "scipy>=1.7.1",


Test patch:
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index fd3b19549..ca8023037 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -26,7 +26,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ["3.10", "3.11"]
+        python-version: ["3.11", "3.12"]
 
     steps:
       - name: Checkout
diff --git a/tests/test_io_data/model_params_baseline_v312.pkl b/tests/test_io_data/model_params_baseline_v312.pkl
new file mode 100644
index 000000000..d9e0c1bd9
Binary files /dev/null and b/tests/test_io_data/model_params_baseline_v312.pkl differ
diff --git a/tests/test_output_plots.py b/tests/test_output_plots.py
index 0aa78fcc2..de4425061 100644
--- a/tests/test_output_plots.py
+++ b/tests/test_output_plots.py
@@ -22,12 +22,18 @@
     base_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl")
     )
-else:
+elif sys.version_info[1] == 11:
     base_params = utils.safe_read_pickle(
         os.path.join(
             CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl"
         )
     )
+else:
+    base_params = utils.safe_read_pickle(
+        os.path.join(
+            CUR_PATH, "test_io_data", "model_params_baseline_v312.pkl"
+        )
+    )
 reform_ss = utils.safe_read_pickle(
     os.path.join(CUR_PATH, "test_io_data", "SS_vars_reform.pkl")
 )
@@ -38,10 +44,16 @@
     reform_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_reform.pkl")
     )
-else:
+elif sys.version_info[1] == 11:
     reform_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_reform_v311.pkl")
     )
+else:
+    reform_params = utils.safe_read_pickle(
+        os.path.join(
+            CUR_PATH, "test_io_data", "model_params_baseline_v312.pkl"
+        )
+    )
 reform_taxfunctions = utils.safe_read_pickle(
     os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_reform.pkl")
 )
@@ -147,7 +159,7 @@ def test_plot_aggregates(
         plot_type=plot_type,
         stationarized=stationarized,
         num_years_to_plot=20,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         forecast_data=np.ones(20),
         forecast_units="ones",
         vertical_line_years=vertical_line_years,
@@ -198,7 +210,7 @@ def test_plot_industry_aggregates(
         var_list=["Y_vec"],
         plot_type=plot_type,
         num_years_to_plot=20,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         forecast_data=np.ones(20),
         forecast_units="ones",
         vertical_line_years=vertical_line_years,
@@ -218,7 +230,11 @@ def test_plot_industry_aggregates(
 def test_plot_aggregates_save_fig(tmpdir):
     path = os.path.join(tmpdir, "test_plot.png")
     output_plots.plot_aggregates(
-        base_tpi, base_params, start_year=2023, plot_type="levels", path=path
+        base_tpi,
+        base_params,
+        start_year=int(base_params.start_year),
+        plot_type="levels",
+        path=path,
     )
     img = mpimg.imread(path)
 
@@ -228,7 +244,10 @@ def test_plot_aggregates_save_fig(tmpdir):
 def test_plot_aggregates_not_a_type(tmpdir):
     with pytest.raises(AssertionError):
         output_plots.plot_aggregates(
-            base_tpi, base_params, start_year=2023, plot_type="levels2"
+            base_tpi,
+            base_params,
+            start_year=int(base_params.start_year),
+            plot_type="levels2",
         )
 
 
@@ -275,7 +294,7 @@ def test_plot_gdp_ratio(
         base_params,
         reform_tpi=reform_tpi,
         reform_params=reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         plot_type=plot_type,
         vertical_line_years=vertical_line_years,
         plot_title=plot_title,
@@ -289,7 +308,7 @@ def test_plot_gdp_ratio_save_fig(tmpdir):
         base_tpi,
         base_params,
         reform_tpi=reform_tpi,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         reform_params=reform_params,
         path=path,
     )
@@ -304,7 +323,7 @@ def test_ability_bar():
         base_params,
         reform_tpi,
         reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         plot_title=" Test Plot Title",
     )
     assert fig
@@ -317,7 +336,7 @@ def test_ability_bar_save_fig(tmpdir):
         base_params,
         reform_tpi,
         reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         path=path,
     )
     img = mpimg.imread(path)
@@ -374,7 +393,7 @@ def test_tpi_profiles(by_j):
         base_params,
         reform_tpi,
         reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         by_j=by_j,
         plot_title=" Test Plot Title",
     )
@@ -404,7 +423,7 @@ def test_tpi_profiles_save_fig(tmpdir):
         base_params,
         reform_tpi,
         reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         path=path,
     )
     img = mpimg.imread(path)
@@ -515,7 +534,7 @@ def test_inequality_plot(
         base_params,
         reform_tpi=reform_tpi,
         reform_params=reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         ineq_measure=ineq_measure,
         pctiles=pctiles,
         plot_type=plot_type,
@@ -530,7 +549,7 @@ def test_inequality_plot_save_fig(tmpdir):
         base_params,
         reform_tpi=reform_tpi,
         reform_params=reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         path=path,
     )
     img = mpimg.imread(path)
diff --git a/tests/test_output_tables.py b/tests/test_output_tables.py
index aa8521938..c915cee68 100644
--- a/tests/test_output_tables.py
+++ b/tests/test_output_tables.py
@@ -22,12 +22,18 @@
     base_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl")
     )
-else:
+elif sys.version_info[1] == 11:
     base_params = utils.safe_read_pickle(
         os.path.join(
             CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl"
         )
     )
+else:
+    base_params = utils.safe_read_pickle(
+        os.path.join(
+            CUR_PATH, "test_io_data", "model_params_baseline_v312.pkl"
+        )
+    )
 reform_ss = utils.safe_read_pickle(
     os.path.join(CUR_PATH, "test_io_data", "SS_vars_reform.pkl")
 )
@@ -38,10 +44,16 @@
     reform_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_reform.pkl")
     )
-else:
+elif sys.version_info[1] == 11:
     reform_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_reform_v311.pkl")
     )
+else:
+    reform_params = utils.safe_read_pickle(
+        os.path.join(
+            CUR_PATH, "test_io_data", "model_params_baseline_v312.pkl"
+        )
+    )
 # add investment tax credit parameter that not in cached parameters
 base_params.inv_tax_credit = np.zeros(
     (base_params.T + base_params.S, base_params.M)
@@ -76,7 +88,7 @@ def test_macro_table(
         base_params,
         reform_tpi=reform_tpi,
         reform_params=reform_params,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         output_type=output_type,
         stationarized=stationarized,
         include_SS=True,
@@ -176,7 +188,7 @@ def test_dynamic_revenue_decomposition(include_business_tax, full_break_out):
         reform_params,
         reform_tpi,
         reform_ss,
-        start_year=2023,
+        start_year=int(base_params.start_year),
         include_business_tax=include_business_tax,
         full_break_out=full_break_out,
     )
diff --git a/tests/test_parameter_plots.py b/tests/test_parameter_plots.py
index 14a4fc387..c5b1b51cf 100644
--- a/tests/test_parameter_plots.py
+++ b/tests/test_parameter_plots.py
@@ -17,12 +17,18 @@
     base_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl")
     )
-else:
+elif sys.version_info[1] == 11:
     base_params = utils.safe_read_pickle(
         os.path.join(
             CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl"
         )
     )
+else:
+    base_params = utils.safe_read_pickle(
+        os.path.join(
+            CUR_PATH, "test_io_data", "model_params_baseline_v312.pkl"
+        )
+    )
 base_taxfunctions = utils.safe_read_pickle(
     os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_baseline.pkl")
 )
@@ -36,10 +42,11 @@
 micro_data = utils.safe_read_pickle(
     os.path.join(CUR_PATH, "test_io_data", "micro_data_dict_for_tests.pkl")
 )
-base_params.rho = np.tile(
-    base_params.rho.reshape(1, base_params.S),
-    (base_params.T + base_params.S, 1),
-)
+if base_params.rho.ndim == 1:
+    base_params.rho = np.tile(
+        base_params.rho.reshape(1, base_params.S),
+        (base_params.T + base_params.S, 1),
+    )
 
 
 def test_plot_imm_rates():
@@ -94,13 +101,15 @@ def test_plot_surv_rates_save_fig(tmpdir):
 
 def test_plot_pop_growth():
     fig = parameter_plots.plot_pop_growth(
-        base_params, start_year=2023, include_title=True
+        base_params, start_year=int(base_params.start_year), include_title=True
     )
     assert fig
 
 
 def test_plot_pop_growth_rates_save_fig(tmpdir):
-    parameter_plots.plot_pop_growth(base_params, start_year=2023, path=tmpdir)
+    parameter_plots.plot_pop_growth(
+        base_params, start_year=int(base_params.start_year), path=tmpdir
+    )
     img = mpimg.imread(os.path.join(tmpdir, "pop_growth_rates.png"))
 
     assert isinstance(img, np.ndarray)
diff --git a/tests/test_parameter_tables.py b/tests/test_parameter_tables.py
index d579a8049..30a4f6543 100644
--- a/tests/test_parameter_tables.py
+++ b/tests/test_parameter_tables.py
@@ -16,12 +16,18 @@
     base_params = utils.safe_read_pickle(
         os.path.join(CUR_PATH, "test_io_data", "model_params_baseline.pkl")
     )
-else:
+elif sys.version_info[1] == 11:
     base_params = utils.safe_read_pickle(
         os.path.join(
             CUR_PATH, "test_io_data", "model_params_baseline_v311.pkl"
         )
     )
+else:
+    base_params = utils.safe_read_pickle(
+        os.path.join(
+            CUR_PATH, "test_io_data", "model_params_baseline_v312.pkl"
+        )
+    )
 base_taxfunctions = utils.safe_read_pickle(
     os.path.join(CUR_PATH, "test_io_data", "TxFuncEst_baseline.pkl")
 )