diff --git a/README.md b/README.md index 95887ffe76..e6349168f3 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,47 @@ python -m pip install -r requirements.txt -r requirements-dev.txt python -m pip install -e . --no-build-isolation ``` +## Running the tests + +The tests are installed with linearmodels. They need pytest 9 and xarray, and +pytest-xdist to run in parallel. + +```bash +python -c "import linearmodels; linearmodels.test(['--skip-slow', '-n', 'auto'])" +``` + +`--skip-slow` skips the slow tests and `--skip-examples` the example notebooks. + +pytest loads every plugin that is installed in the environment, and the test +configuration of linearmodels is strict, so that, for example, a marker that is +not registered is an error. A plugin that uses such a marker makes tests fail +with a message such as `'gpu' not found in markers configuration option`. To run +the tests without the other plugins, turn off their automatic loading and load +pytest-xdist explicitly. This works with `python -m pytest` too. + +```bash +PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -c "import linearmodels; linearmodels.test(['-p', 'xdist', '-n', 'auto'])" +``` + +When the tests run in several processes with pytest-xdist, limit the BLAS +libraries to one thread in each process. Otherwise every process starts as many +threads as there are cores, and excessive context switching makes the tests run +very slow. This problem is particularly acute on machines with high CPU counts. + +```bash +export MKL_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 +export OMP_NUM_THREADS=1 +``` + +or, in PowerShell, + +```powershell +$env:MKL_NUM_THREADS = 1 +$env:OPENBLAS_NUM_THREADS = 1 +$env:OMP_NUM_THREADS = 1 +``` + ## Documentation [Stable Documentation](https://bashtage.github.io/linearmodels/) is @@ -173,6 +214,8 @@ Python, NumPy and the other dependencies. ### Testing - pytest (9.0+) +- xarray (0.16+) +- pytest-xdist (optional, to run the tests in parallel) ### Documentation diff --git a/linearmodels/tests/iv/test_new_feature_hardening.py b/linearmodels/tests/iv/test_new_feature_hardening.py index 34abc15d51..09caa10d0b 100644 --- a/linearmodels/tests/iv/test_new_feature_hardening.py +++ b/linearmodels/tests/iv/test_new_feature_hardening.py @@ -88,7 +88,9 @@ def run_docstring_examples(obj, name): failures = [] with_examples = set() for test in finder.find(obj, name, globs={}): - runner = doctest.DocTestRunner(optionflags=doctest.ELLIPSIS) + # verbose=None, the default, is verbose when "-v" is in sys.argv, as it is in + # "pytest -v", and the examples that pass are then written to out as well + runner = doctest.DocTestRunner(verbose=False, optionflags=doctest.ELLIPSIS) with warnings.catch_warnings(): warnings.simplefilter("ignore", MissingValueWarning) runner.run(test, out=failures.append) diff --git a/linearmodels/tests/test_kernel_bandwidth_few_observations.py b/linearmodels/tests/test_kernel_bandwidth_few_observations.py index f0bb92c35c..2347adb56b 100644 --- a/linearmodels/tests/test_kernel_bandwidth_few_observations.py +++ b/linearmodels/tests/test_kernel_bandwidth_few_observations.py @@ -874,6 +874,29 @@ def system_equations(nobs, endogenous=False): } +def gmm_equations(nobs): + """ + Two equations, each with one endogenous regressor and one instrument + + IVSystemGMM inverts the covariance of the moment conditions, in the robust and + the kernel weights. The equations of system_equations(nobs, True) are exactly + identified, so that their 4 moment conditions sum to zero at the estimates and have + rank nobs - 1 or less: their covariance is singular with 4 or fewer observations, + and inverting it only works up to rounding (it raised "Singular matrix" on FreeBSD, + #853). These equations have no constant and 2 moment conditions, whose covariance + has full rank from 3 observations. + """ + first, second = iv_data(nobs, seed=11), iv_data(nobs, seed=111) + return { + f"eq{i}": { + "dependent": data.y, + "endog": data[["xn"]], + "instruments": data[["z1"]], + } + for i, data in enumerate((first, second)) + } + + @pytest.mark.parametrize("kernel", CANONICAL) @pytest.mark.parametrize(("model", "endogenous"), [(SUR, False), (IV3SLS, True)]) def test_system_models_raise_when_fit_with_too_few_observations( @@ -914,7 +937,7 @@ def test_system_models_explicit_bandwidth_works_with_the_observations_that_raise def test_system_gmm_weights_raise_when_fit_with_too_few_observations(kernel): nobs = fewest_observations(kernel) - 1 mod = IVSystemGMM( - system_equations(nobs, True), + gmm_equations(nobs), weight_type="kernel", kernel=kernel, optimal_bw=True, @@ -923,20 +946,24 @@ def test_system_gmm_weights_raise_when_fit_with_too_few_observations(kernel): mod.fit() -# The robust weights of the two equations need 4 observations before the covariance -# gets to the bandwidth: the Bartlett kernel is reached with 4 -SYSTEM_GMM_COVARIANCE_FEW = [("parzen", 4), ("qs", 4), ("qs", 5)] +# Every number of observations from 3, where the robust weights of gmm_equations can +# be estimated, to one less than the kernel needs +SYSTEM_GMM_COVARIANCE_FEW = [ + (kernel, nobs) + for kernel in CANONICAL + for nobs in range(3, fewest_observations(kernel)) +] @pytest.mark.parametrize(("kernel", "nobs"), SYSTEM_GMM_COVARIANCE_FEW) def test_system_gmm_covariance_raises_when_fit_with_too_few_observations(kernel, nobs): - mod = IVSystemGMM(system_equations(nobs, True)) + mod = IVSystemGMM(gmm_equations(nobs)) with pytest.raises(ValueError, match=re.escape(too_few_message(kernel, nobs))): mod.fit(cov_type="kernel", kernel=kernel) def test_system_gmm_default_bandwidth_raises_when_fit_with_too_few_observations(): - mod = IVSystemGMM(system_equations(3, True), weight_type="kernel") + mod = IVSystemGMM(gmm_equations(3), weight_type="kernel") with pytest.warns(DefaultBandwidthWarning): with pytest.raises(ValueError, match=re.escape(too_few_message("bartlett", 3))): mod.fit() @@ -945,7 +972,7 @@ def test_system_gmm_default_bandwidth_raises_when_fit_with_too_few_observations( @pytest.mark.parametrize("kernel", CANONICAL) def test_system_gmm_explicit_bandwidth_works_with_the_observations_that_raise(kernel): nobs = fewest_observations(kernel) - 1 - equations = system_equations(nobs, True) + equations = gmm_equations(nobs) explicit = IVSystemGMM( equations, weight_type="kernel", kernel=kernel, bandwidth=1 ).fit() @@ -959,7 +986,7 @@ def test_system_gmm_explicit_bandwidth_works_with_the_observations_that_raise(ke @pytest.mark.parametrize("kernel", CANONICAL) def test_system_gmm_with_the_fewest_observations(kernel): nobs = fewest_observations(kernel) - equations = system_equations(nobs, True) + equations = gmm_equations(nobs) res = IVSystemGMM( equations, weight_type="kernel", kernel=kernel, optimal_bw=True ).fit(cov_type="kernel", kernel=kernel)