mc3 3.2.1__tar.gz → 3.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. mc3-3.3.0/.github/workflows/python-package.yml +47 -0
  2. mc3-3.3.0/MANIFEST.in +12 -0
  3. {mc3-3.2.1 → mc3-3.3.0}/PKG-INFO +6 -24
  4. mc3-3.3.0/docs/docs_requirements.txt +6 -0
  5. {mc3-3.2.1 → mc3-3.3.0}/docs/get_started.rst +1 -1
  6. {mc3-3.2.1 → mc3-3.3.0}/docs/index.rst +1 -1
  7. {mc3-3.2.1 → mc3-3.3.0}/mc3/__init__.py +1 -1
  8. {mc3-3.2.1 → mc3-3.3.0}/mc3/__main__.py +1 -1
  9. {mc3-3.2.1 → mc3-3.3.0}/mc3/chain.py +1 -1
  10. {mc3-3.2.1 → mc3-3.3.0}/mc3/fit_driver.py +1 -1
  11. {mc3-3.2.1 → mc3-3.3.0}/mc3/mcmc_driver.py +1 -1
  12. {mc3-3.2.1 → mc3-3.3.0}/mc3/plots/__init__.py +1 -1
  13. {mc3-3.2.1 → mc3-3.3.0}/mc3/plots/colors.py +1 -1
  14. {mc3-3.2.1 → mc3-3.3.0}/mc3/plots/plot_functions.py +1 -1
  15. {mc3-3.2.1 → mc3-3.3.0}/mc3/plots/posterior.py +58 -23
  16. {mc3-3.2.1 → mc3-3.3.0}/mc3/sampler_driver.py +1 -1
  17. {mc3-3.2.1 → mc3-3.3.0}/mc3/stats/__init__.py +3 -1
  18. mc3-3.3.0/mc3/stats/dwt.py +229 -0
  19. {mc3-3.2.1 → mc3-3.3.0}/mc3/stats/gelman.py +1 -1
  20. {mc3-3.2.1 → mc3-3.3.0}/mc3/stats/prayer.py +1 -1
  21. {mc3-3.2.1 → mc3-3.3.0}/mc3/stats/stats.py +78 -147
  22. mc3-3.3.0/mc3/stats/time_averaging.py +199 -0
  23. {mc3-3.2.1 → mc3-3.3.0}/mc3/utils/__init__.py +1 -1
  24. {mc3-3.2.1 → mc3-3.3.0}/mc3/utils/log.py +1 -1
  25. {mc3-3.2.1 → mc3-3.3.0}/mc3/utils/utils.py +1 -1
  26. {mc3-3.2.1 → mc3-3.3.0}/mc3/version.py +3 -3
  27. {mc3-3.2.1 → mc3-3.3.0}/mc3.egg-info/PKG-INFO +6 -24
  28. {mc3-3.2.1 → mc3-3.3.0}/mc3.egg-info/SOURCES.txt +3 -15
  29. {mc3-3.2.1 → mc3-3.3.0}/mc3.egg-info/requires.txt +1 -1
  30. {mc3-3.2.1 → mc3-3.3.0}/pyproject.toml +4 -4
  31. {mc3-3.2.1 → mc3-3.3.0}/requirements.txt +1 -1
  32. {mc3-3.2.1 → mc3-3.3.0}/tests/test_fit.py +1 -1
  33. {mc3-3.2.1 → mc3-3.3.0}/tests/test_logging.py +1 -1
  34. {mc3-3.2.1 → mc3-3.3.0}/tests/test_mcmc.py +1 -1
  35. {mc3-3.2.1 → mc3-3.3.0}/tests/test_plots.py +1 -1
  36. {mc3-3.2.1 → mc3-3.3.0}/tests/test_stats.py +39 -34
  37. {mc3-3.2.1 → mc3-3.3.0}/tests/test_utils.py +1 -1
  38. mc3-3.2.1/MANIFEST.in +0 -19
  39. mc3-3.2.1/Makefile +0 -46
  40. mc3-3.2.1/docs/figures/quad_bestfit.png +0 -0
  41. mc3-3.2.1/docs/figures/quad_fitting.png +0 -0
  42. mc3-3.2.1/docs/figures/quad_hist.png +0 -0
  43. mc3-3.2.1/docs/figures/quad_pairwise.png +0 -0
  44. mc3-3.2.1/docs/figures/quad_trace.png +0 -0
  45. mc3-3.2.1/docs/figures/rms-vs-binsize.png +0 -0
  46. mc3-3.2.1/mc3/stats/time_averaging.py +0 -60
  47. mc3-3.2.1/setup.py +0 -38
  48. mc3-3.2.1/src_c/_binarray.c +0 -107
  49. mc3-3.2.1/src_c/_chisq.c +0 -167
  50. mc3-3.2.1/src_c/_dwt.c +0 -216
  51. mc3-3.2.1/src_c/_time_averaging.c +0 -171
  52. mc3-3.2.1/src_c/include/ind.h +0 -9
  53. mc3-3.2.1/src_c/include/stats.h +0 -224
  54. mc3-3.2.1/src_c/include/wavelet.h +0 -128
  55. {mc3-3.2.1 → mc3-3.3.0}/.readthedocs.yml +0 -0
  56. {mc3-3.2.1 → mc3-3.3.0}/CONTRIBUTING.md +0 -0
  57. {mc3-3.2.1 → mc3-3.3.0}/LICENSE +0 -0
  58. {mc3-3.2.1 → mc3-3.3.0}/README.md +0 -0
  59. {mc3-3.2.1 → mc3-3.3.0}/docs/Makefile +0 -0
  60. {mc3-3.2.1 → mc3-3.3.0}/docs/api.rst +0 -0
  61. {mc3-3.2.1 → mc3-3.3.0}/docs/conf.py +0 -0
  62. {mc3-3.2.1 → mc3-3.3.0}/docs/contributing.rst +0 -0
  63. {mc3-3.2.1 → mc3-3.3.0}/docs/fit_tutorial.rst +0 -0
  64. {mc3-3.2.1 → mc3-3.3.0}/docs/license.rst +0 -0
  65. {mc3-3.2.1 → mc3-3.3.0}/docs/make.bat +0 -0
  66. {mc3-3.2.1 → mc3-3.3.0}/docs/mcmc_tutorial.rst +0 -0
  67. {mc3-3.2.1 → mc3-3.3.0}/docs/references.rst +0 -0
  68. {mc3-3.2.1 → mc3-3.3.0}/docs/time_averaging.rst +0 -0
  69. {mc3-3.2.1 → mc3-3.3.0}/examples/get_started.py +0 -0
  70. {mc3-3.2.1 → mc3-3.3.0}/examples/timeavg.py +0 -0
  71. {mc3-3.2.1 → mc3-3.3.0}/examples/tutorial.py +0 -0
  72. {mc3-3.2.1 → mc3-3.3.0}/mc3.egg-info/dependency_links.txt +0 -0
  73. {mc3-3.2.1 → mc3-3.3.0}/mc3.egg-info/entry_points.txt +0 -0
  74. {mc3-3.2.1 → mc3-3.3.0}/mc3.egg-info/top_level.txt +0 -0
  75. {mc3-3.2.1 → mc3-3.3.0}/pytest.ini +0 -0
  76. {mc3-3.2.1 → mc3-3.3.0}/setup.cfg +0 -0
@@ -0,0 +1,47 @@
1
+ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3
+
4
+ name: Tests
5
+
6
+ on:
7
+ push:
8
+ branches: [ "*" ]
9
+ pull_request:
10
+ branches: [ "main" ]
11
+ schedule:
12
+ - cron: '0 12 1 * *'
13
+
14
+ jobs:
15
+ build:
16
+
17
+ runs-on: ${{ matrix.os }}
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
22
+ os: [ubuntu-latest]
23
+
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+ - name: Set up Python ${{ matrix.python-version }}
27
+ uses: actions/setup-python@v6
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+ - name: Install dependencies
31
+ run: |
32
+ python -m pip install --upgrade pip
33
+ python -m pip install flake8 pytest
34
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
35
+
36
+ - name: Install package
37
+ run: |
38
+ python -m pip install -e .
39
+ - name: Lint with flake8
40
+ run: |
41
+ # stop the build if there are Python syntax errors or undefined names
42
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
43
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
44
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
45
+ - name: Test with pytest
46
+ run: |
47
+ pytest tests -v
mc3-3.3.0/MANIFEST.in ADDED
@@ -0,0 +1,12 @@
1
+ include MANIFEST.in
2
+ include CONTRIBUTING.md
3
+ include README.md
4
+ include LICENSE
5
+ include pyproject.toml
6
+ include requirements.txt
7
+ include pytest.ini
8
+ include .readthedocs.yml
9
+ include .github/workflows/python-package.yml
10
+ include tests/*
11
+ include docs/*
12
+ include examples/*.py
@@ -1,37 +1,19 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mc3
3
- Version: 3.2.1
3
+ Version: 3.3.0
4
4
  Summary: Multi-core Markov-chain Monte Carlo package
5
5
  Author-email: Patricio Cubillos <patricio.cubillos@oeaw.ac.at>
6
- License: Copyright (c) 2015-2022 Patricio Cubillos and contributors.
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy
9
- of this software and associated documentation files (the "Software"), to deal
10
- in the Software without restriction, including without limitation the rights
11
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- copies of the Software, and to permit persons to whom the Software is
13
- furnished to do so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in
16
- all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
- THE SOFTWARE.
25
-
6
+ License-Expression: MIT
26
7
  Project-URL: Homepage, https://github.com/pcubillos/mc3
27
- Requires-Python: >=3.9
8
+ Requires-Python: >=3.10
28
9
  Description-Content-Type: text/markdown
29
10
  License-File: LICENSE
30
- Requires-Dist: numpy>=1.23.5
11
+ Requires-Dist: numpy>=2.0.0
31
12
  Requires-Dist: scipy>=1.5.4
32
13
  Requires-Dist: matplotlib>=3.3.4
33
14
  Provides-Extra: test
34
15
  Requires-Dist: pytest>=6.0; extra == "test"
16
+ Dynamic: license-file
35
17
 
36
18
  # mc3: Multi-core Markov-chain Monte Carlo
37
19
  > A Python implementation of the Markov-chain Monte Carlo algorithm.
@@ -0,0 +1,6 @@
1
+ sphinx==8.1.3
2
+ sphinx-rtd-theme>=2.0.0
3
+ nbsphinx
4
+ sphinx_copybutton
5
+ ipython>=8.17
6
+
@@ -6,7 +6,7 @@ Getting Started
6
6
  System Requirements
7
7
  -------------------
8
8
 
9
- ``mc3`` is compatible with Python 3.9+, and has been `tested
9
+ ``mc3`` is compatible with Python 3.10+, and is `tested
10
10
  <https://github.com/pcubillos/mc3/actions/workflows/python-package.yml>`_ to work on Unix/Linux and OS X machines.
11
11
 
12
12
  Install
@@ -40,7 +40,7 @@ mc3: Multi-Core Markov-Chain Monte Carlo
40
40
 
41
41
  .. note::
42
42
 
43
- Got Python3.9+? Simply install as: ``pip install mc3``
43
+ Compatible with Python3.10. Install as: ``pip install mc3``
44
44
 
45
45
 
46
46
  Features
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  from .sampler_driver import *
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  import sys
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  import sys
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  from .plot_functions import *
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [
@@ -140,6 +140,8 @@ def _histogram(
140
140
  hist_kw['facecolor'] = 'none'
141
141
 
142
142
  for i in range(npars):
143
+ if np.any(np.isnan(posterior[:,i])):
144
+ continue
143
145
  ax = axes[i]
144
146
  if clear:
145
147
  ax.clear()
@@ -533,6 +535,7 @@ class Marginal():
533
535
  bins=25, fontsize=11, linewidth=1.5,
534
536
  axes=None,
535
537
  show_texts=True, show_estimates=True,
538
+ alpha=0.6,
536
539
  ):
537
540
  self.source = source
538
541
  self.fig = None
@@ -544,6 +547,7 @@ class Marginal():
544
547
  self.bestp = bestp
545
548
  self.ranges = ranges
546
549
  self.theme = theme
550
+ self.alpha = alpha
547
551
 
548
552
  if nx is None or ny is None:
549
553
  # Default layout:
@@ -630,6 +634,7 @@ class Marginal():
630
634
  hpd_min, self.source.low_bounds, self.source.high_bounds,
631
635
  self.linewidth, self.theme,
632
636
  self.orientation,
637
+ alpha=self.alpha,
633
638
  top_pad=1.25,
634
639
  )
635
640
  _plot_marginal(self)
@@ -637,7 +642,7 @@ class Marginal():
637
642
  if savefile is not None:
638
643
  self.fig.savefig(savefile, dpi=300)
639
644
 
640
- def overplot(self, post, labels=None, nlevels=4, alpha=0.4):
645
+ def overplot(self, posts, labels=None, nlevels=4, alpha=0.4, axes_mask=None):
641
646
  """
642
647
  Overplot additional posteriors in the same figure.
643
648
 
@@ -659,25 +664,39 @@ class Marginal():
659
664
  length of labels has to be one more than posts, because
660
665
  it also contains the label for self.
661
666
  """
662
- if '_like' in post.statistics:
663
- hpd_min = post.hpd_min
664
- else:
667
+ all_posts = [self.source] + list(posts)
668
+ for i,post in enumerate(posts):
669
+ estimates = post.estimates
670
+ if not self.show_estimates:
671
+ estimates = [None for _ in estimates]
665
672
  hpd_min = None
673
+ if '_like' in self.statistics:
674
+ hpd_min = self.source.hpd_min
666
675
 
667
- estimates = post.estimates
668
- if not self.show_estimates:
669
- estimates = [None for _ in estimates]
676
+ _histogram(
677
+ post.posterior, estimates, self.ranges,
678
+ self.hist_axes, self.bins,
679
+ post.pdf, post.xpdf,
680
+ hpd_min, post.low_bounds, post.high_bounds,
681
+ self.linewidth, post.theme,
682
+ post.orientation, alpha=alpha,
683
+ clear=False,
684
+ )
685
+ if not self.show_texts:
686
+ return
687
+ text_cols = [post.theme.color for post in all_posts]
688
+ for text in self.stats_texts:
689
+ text.set_visible(False)
690
+ self.stats_texts = []
670
691
 
671
- _histogram(
672
- post.posterior, estimates, post.ranges,
673
- self.hist_axes, self.bins,
674
- post.pdf, post.xpdf,
675
- hpd_min, post.low_bounds, post.high_bounds,
676
- self.linewidth, post.theme,
677
- post.orientation,
678
- top_pad=1.25,
679
- clear=False,
680
- )
692
+ for j in range(self.npars):
693
+ stats_texts = [
694
+ '' if post.mask[j] else rf'{post.tex_estimates[j]}'
695
+ for post in all_posts
696
+ ]
697
+ self.stats_texts += colors.rainbow_text(
698
+ self.hist_axes[j], stats_texts, self.fontsize, text_cols,
699
+ )
681
700
 
682
701
 
683
702
  class Figure(Marginal):
@@ -694,10 +713,11 @@ class Figure(Marginal):
694
713
  bins=25, nlevels=6, fontsize=None, linewidth=None,
695
714
  show_texts=True, show_estimates=True,
696
715
  show_colorbar=True,
716
+ figure=None,
697
717
  fignum=None,
698
718
  ):
699
719
  self.source = source
700
- self.fig = None
720
+ self.fig = figure
701
721
  self.pair_axes = None
702
722
  self.hist_axes = None
703
723
  self.posterior = posterior
@@ -934,7 +954,7 @@ class Figure(Marginal):
934
954
  post.posterior, estimates, self.ranges, self.hist_axes,
935
955
  self.bins, post.pdf, post.xpdf,
936
956
  hpd_min, post.low_bounds, post.high_bounds,
937
- self.linewidth, post.theme, post.orientation, alpha=0.5,
957
+ self.linewidth, post.theme, post.orientation, alpha=alpha,
938
958
  clear=False,
939
959
  )
940
960
  if not self.show_texts:
@@ -1044,7 +1064,7 @@ class StatisticsUpdate(ShareUpdate):
1044
1064
  )
1045
1065
 
1046
1066
 
1047
- class Posterior(object):
1067
+ class Posterior():
1048
1068
  """
1049
1069
  Classification of posterior plotting tools.
1050
1070
 
@@ -1090,6 +1110,7 @@ class Posterior(object):
1090
1110
  show_texts=True, show_estimates=True,
1091
1111
  show_colorbar=True,
1092
1112
  seed=314159,
1113
+ mask=None,
1093
1114
  ):
1094
1115
  self.figures = []
1095
1116
  nsamples, self.npars = np.shape(posterior)
@@ -1131,7 +1152,9 @@ class Posterior(object):
1131
1152
  self.bestp = bestp
1132
1153
  self.statistics = statistics
1133
1154
  self.quantile = quantile
1134
-
1155
+ if mask is None:
1156
+ mask = np.ones(self.npars, bool)
1157
+ self.mask = mask
1135
1158
 
1136
1159
  def plot(
1137
1160
  self, plot_marginal=True,
@@ -1143,6 +1166,7 @@ class Posterior(object):
1143
1166
  show_texts=None, show_estimates=None,
1144
1167
  show_colorbar=None,
1145
1168
  savefile=None,
1169
+ nlevels=None,
1146
1170
  ):
1147
1171
  """
1148
1172
  Plot marginal histograms and pairwise posteriors.
@@ -1156,6 +1180,8 @@ class Posterior(object):
1156
1180
  show_texts = self.show_texts
1157
1181
  if show_colorbar is None:
1158
1182
  show_colorbar = self.show_colorbar
1183
+ if nlevels is None:
1184
+ nlevels = 6
1159
1185
 
1160
1186
  fig = Figure(
1161
1187
  self,
@@ -1174,7 +1200,8 @@ class Posterior(object):
1174
1200
  show_texts=show_texts,
1175
1201
  show_estimates=show_estimates,
1176
1202
  show_colorbar=show_colorbar,
1177
- # bins=25, nlevels=20,
1203
+ # bins=25,
1204
+ nlevels=nlevels,
1178
1205
  )
1179
1206
  self.figures.append(fig)
1180
1207
  fig.plot(figure=figure, savefile=savefile)
@@ -1185,7 +1212,9 @@ class Posterior(object):
1185
1212
  self, fignum=None, axes=None, quantile=None,
1186
1213
  nx=None, ny=None,
1187
1214
  savefile=None,
1215
+ fontsize=11, linewidth=1.5,
1188
1216
  show_texts=None, show_estimates=None,
1217
+ alpha=0.6,
1189
1218
  ):
1190
1219
  """
1191
1220
  Plot histogram of marginal posteriors.
@@ -1194,6 +1223,8 @@ class Posterior(object):
1194
1223
  show_estimates = self.show_estimates
1195
1224
  if show_texts is None:
1196
1225
  show_texts = self.show_texts
1226
+ if quantile is None:
1227
+ quantile = self.quantile
1197
1228
 
1198
1229
  fig = Marginal(
1199
1230
  self,
@@ -1201,8 +1232,12 @@ class Posterior(object):
1201
1232
  self.ranges, self.theme,
1202
1233
  statistics=self.statistics,
1203
1234
  nx=nx, ny=ny,
1235
+ quantile=quantile,
1236
+ fontsize=fontsize,
1237
+ linewidth=linewidth,
1204
1238
  show_texts=show_texts,
1205
1239
  show_estimates=show_estimates,
1240
+ alpha=alpha,
1206
1241
  )
1207
1242
  self.figures.append(fig)
1208
1243
  fig.plot(savefile=savefile, fignum=fignum, axes=axes)
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [
@@ -1,15 +1,17 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  from .gelman import *
5
5
  from .stats import *
6
6
  from .time_averaging import *
7
+ from .dwt import *
7
8
  from .prayer import *
8
9
 
9
10
  __all__ = (
10
11
  gelman.__all__
11
12
  + stats.__all__
12
13
  + time_averaging.__all__
14
+ + dwt.__all__
13
15
  + prayer.__all__
14
16
  )
15
17
 
@@ -0,0 +1,229 @@
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
+ # mc3 is open-source software under the MIT license (see LICENSE).
3
+
4
+ __all__ = [
5
+ 'daubechies4',
6
+ 'dwt_daub4',
7
+ 'dwt_chisq',
8
+ ]
9
+
10
+ import numpy as np
11
+
12
+
13
+ def daubechies4(array, n, inverse):
14
+ """
15
+ Applies the Daubechies-4 discrete wavelet transform (DWT)
16
+ or its inverse (transpose filter) depending on isign.
17
+
18
+ Parameters
19
+ ----------
20
+ a : np.ndarray
21
+ Input signal (1D array of length n).
22
+ Modified in place.
23
+ n: int
24
+ Hierarchy level of the transform.
25
+ inverse: bool
26
+ Calculate forward (False) or inverse (True) DWT
27
+ """
28
+ C0 = 0.48296291314453410
29
+ C1 = 0.83651630373780772
30
+ C2 = 0.22414386804201339
31
+ C3 = -0.12940952255126034
32
+
33
+ if len(array) < 4:
34
+ return
35
+
36
+ dwt = np.zeros(n)
37
+ k = n >> 1 # n/2 using bit shift
38
+
39
+ if inverse:
40
+ dwt[0] = C2*array[k-1] + C1*array[n-1] + C0*array[0] + C3*array[k]
41
+ dwt[1] = C3*array[k-1] - C0*array[n-1] + C1*array[0] - C2*array[k]
42
+ j = 2
43
+ for i in range(0, k-1):
44
+ dwt[j] = C2*array[i] + C1*array[i+k] + C0*array[i+1] + C3*array[i+k+1]
45
+ dwt[j+1] = C3*array[i] - C0*array[i+k] + C1*array[i+1] - C2*array[i+k+1]
46
+ j += 2
47
+ else: # Forward transform
48
+ i = 0
49
+ for j in range(0, n-3, 2):
50
+ dwt[i] = C0*array[j] + C1*array[j+1] + C2*array[j+2] + C3*array[j+3]
51
+ dwt[i+k] = C3*array[j] - C2*array[j+1] + C1*array[j+2] - C0*array[j+3]
52
+ i += 1
53
+ # Wrap-around (circular convolution)
54
+ dwt[i] = C0*array[n-2] + C1*array[n-1] + C2*array[0] + C3*array[1]
55
+ dwt[i+k] = C3*array[n-2] - C2*array[n-1] + C1*array[0] - C0*array[1]
56
+
57
+ # Copy back into input array
58
+ array[0:n] = dwt
59
+
60
+
61
+ def dwt_daub4(array, inverse=False):
62
+ """
63
+ 1D discrete wavelet transform using the Daubechies 4-parameter wavelet
64
+ Implements the pyramid algorithm replacing array by its DWT or inverse DWT
65
+
66
+ Parameters
67
+ ----------
68
+ array: 1D float array
69
+ Input data array to which to apply the DWT (modified in place)
70
+ inverse: bool
71
+ Calculate forward (False) or inverse (True) DWT
72
+
73
+ Notes
74
+ -----
75
+ The input vector must have length 2**M with M an integer, otherwise
76
+ the output will zero-padded to the next size of the form 2**M.
77
+
78
+ Examples
79
+ --------
80
+ >>> import numpy as np
81
+ >>> import matplotlib.pyplot as plt
82
+ >>> import mc3.stats as ms
83
+
84
+ >>> # Calculate the inverse DWT for a unit vector:
85
+ >>> nx = 1000
86
+ >>> array = np.zeros(nx)
87
+ >>> array[10:20] = 1.0
88
+ >>> inverse = True
89
+ >>> idwt = ms.dwt_daub4(array, inverse=inverse)
90
+ >>> dwt_array = dawt(array, inverse=inverse)
91
+
92
+ >>> # Plot the inverse DWT
93
+ >>> fig = plt.figure(10)
94
+ >>> fig.set_size_inches(7,4)
95
+ >>> plt.clf()
96
+ >>> plt.subplots_adjust(0.1, 0.1, 0.99, 0.96)
97
+ >>> plt.plot(1-dwt_array/idwt, label='py DWT', color='k')
98
+ >>> plt.plot(0.1*array, label='signal', color='tomato')
99
+ >>> plt.plot(idwt, label='DWT', color='xkcd:blue')
100
+ >>> plt.plot(dwt_array, label='py DWT', color='limegreen', dashes=(8,3))
101
+ """
102
+ size = len(array)
103
+ M = np.ceil(np.log2(size))
104
+ dwt_size = 2**int(M)
105
+
106
+ dwt_array = np.zeros(dwt_size)
107
+ dwt_array[0:size] = array
108
+
109
+ if size < 4:
110
+ return dwt_array
111
+
112
+ if inverse:
113
+ # Inverse transform: start from smallest hierarchy to largest
114
+ n = 4
115
+ while n <= dwt_size:
116
+ daubechies4(dwt_array, n, inverse)
117
+ n <<= 1
118
+ else:
119
+ # Forward transform: start from largest hierarchy to smallest
120
+ n = dwt_size
121
+ while n >= 4:
122
+ daubechies4(dwt_array, n, inverse)
123
+ n >>= 1
124
+
125
+ return dwt_array
126
+
127
+
128
+ def dwt_chisq(model, data, params, priors=None, priorlow=None, priorup=None):
129
+ """
130
+ Calculate -2*ln(likelihood) in a wavelet-base (a pseudo chi-squared)
131
+ based on Carter & Winn (2009), ApJ 704, 51.
132
+
133
+ Parameters
134
+ ----------
135
+ model: 1D ndarray
136
+ Model fit of data.
137
+ data: 1D ndarray
138
+ Data set array fitted by model.
139
+ params: 1D float ndarray
140
+ Model parameters (including the tree noise parameters: gamma,
141
+ sigma_r, sigma_w; which must be the last three elements in params).
142
+ priors: 1D ndarray
143
+ Parameter prior values.
144
+ priorlow: 1D ndarray
145
+ Left-sided prior standard deviation (param < prior).
146
+ A priorlow value of zero denotes a uniform prior.
147
+ priorup: 1D ndarray
148
+ Right-sided prior standard deviation (prior < param).
149
+ A priorup value of zero denotes a uniform prior.
150
+
151
+ Returns
152
+ -------
153
+ chisq: Float
154
+ Wavelet-based (pseudo) chi-squared.
155
+
156
+ Notes
157
+ -----
158
+ - If the residuals array size is not of the form 2**N, the routine
159
+ zero-pads the array until this condition is satisfied.
160
+
161
+ Examples
162
+ --------
163
+ >>> import mc3.stats as ms
164
+ >>> import numpy as np
165
+ >>> # Compute chi-squared for a given model fitting a data set:
166
+ >>> data = np.array([2.0, 0.0, 3.0, -2.0, -1.0, 2.0, 2.0, 0.0])
167
+ >>> model = np.ones(len(data))
168
+ >>> params = np.array([1.0, 0.1, 0.1])
169
+ >>> chisq = ms.dwt_chisq(model, data, params)
170
+ >>> print(chisq)
171
+ 1693.22308882
172
+
173
+ >>> # Now, say this is a three-parameter model, with a Gaussian prior
174
+ >>> # on the last parameter:
175
+ >>> priors = np.array([1.0, 0.2, 0.3])
176
+ >>> plow = np.array([0.0, 0.0, 0.1])
177
+ >>> pup = np.array([0.0, 0.0, 0.1])
178
+ >>> chisq = ms.dwt_chisq(model, data, params, priors, plow, pup)
179
+ >>> print(chisq)
180
+ 1697.2230888243134
181
+ """
182
+ if len(params) < 3:
183
+ raise ValueError('Wavelet chisq should have at least three parameters')
184
+
185
+ # g-factor of the covariance of the wavelet coefficients:
186
+ # g(gamma=1) = 1.0 / (2*ln(2))
187
+ g = 0.72134752
188
+
189
+ size = len(data)
190
+ M = int(np.ceil(np.log2(size)))
191
+
192
+ # Expand (zero padding) to res to a size proportional to 2**M (zero padding)
193
+ residuals = np.zeros(2**M)
194
+ residuals[0:size] = data - model
195
+ dwt_residuals = dwt_daub4(residuals, inverse=False)
196
+
197
+ gamma, sigma_r, sigma_w = params[-3:]
198
+ # Equation (34) of CW2009, square of sigma_S
199
+ sigma_S = g * sigma_r**2.0 * 2.0**-gamma + sigma_w**2.0
200
+ # Second term in right-hand side of equation 32
201
+ chisq = (
202
+ (dwt_residuals[0]**2.0 + dwt_residuals[1]**2.0) / sigma_S +
203
+ 2.0*np.log(2*np.pi*sigma_S)
204
+ )
205
+
206
+ # Equation (33) of CW2009, sigma_W squared
207
+ for m in range(1, M): # Number of scales
208
+ sigma_W = sigma_r**2.0 * 2.0**(-gamma*m) + sigma_w**2.0
209
+ n = 2**m
210
+ res_sq = np.sum(dwt_residuals[n:2*n]**2.0)
211
+ chisq += res_sq/sigma_W + n*np.log(2*np.pi*sigma_W)
212
+
213
+ # Add priors contribution
214
+ if priors is None or priorlow is None or priorup is None:
215
+ return chisq
216
+
217
+ delta_priors = params - priors
218
+ for d_prior, e_low, e_up in zip(delta_priors, priorlow, priorup):
219
+ # No prior
220
+ if e_low<=0 or e_up<=0:
221
+ continue
222
+ # Gaussian prior
223
+ if d_prior > 0:
224
+ chisq += (d_prior/e_up)**2.0
225
+ else:
226
+ chisq += (d_prior/e_low)**2.0
227
+
228
+ return chisq
229
+
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2015-2025 Patricio Cubillos and contributors.
1
+ # Copyright (c) 2015-2026 Patricio Cubillos and contributors.
2
2
  # mc3 is open-source software under the MIT license (see LICENSE).
3
3
 
4
4
  __all__ = [