adjeff 1.2.1__tar.gz → 1.2.2__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 (130) hide show
  1. {adjeff-1.2.1 → adjeff-1.2.2}/CHANGELOG.md +12 -3
  2. {adjeff-1.2.1 → adjeff-1.2.2}/PKG-INFO +1 -1
  3. {adjeff-1.2.1 → adjeff-1.2.2}/pyproject.toml +1 -1
  4. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/sweep_sampler.py +27 -9
  5. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_atmo_samplers.py +38 -4
  6. {adjeff-1.2.1 → adjeff-1.2.2}/.gitattributes +0 -0
  7. {adjeff-1.2.1 → adjeff-1.2.2}/.github/workflows/ci.yml +0 -0
  8. {adjeff-1.2.1 → adjeff-1.2.2}/.github/workflows/publish.yml +0 -0
  9. {adjeff-1.2.1 → adjeff-1.2.2}/.gitignore +0 -0
  10. {adjeff-1.2.1 → adjeff-1.2.2}/.pre-commit-config.yaml +0 -0
  11. {adjeff-1.2.1 → adjeff-1.2.2}/LICENSE +0 -0
  12. {adjeff-1.2.1 → adjeff-1.2.2}/README.md +0 -0
  13. {adjeff-1.2.1 → adjeff-1.2.2}/docs/guide.md +0 -0
  14. {adjeff-1.2.1 → adjeff-1.2.2}/docs/plan-observabilite-0.13.0.md +0 -0
  15. {adjeff-1.2.1 → adjeff-1.2.2}/docs/plan-smartg-2.0-0.15.0.md +0 -0
  16. {adjeff-1.2.1 → adjeff-1.2.2}/docs/revue-simplification-2026-08.md +0 -0
  17. {adjeff-1.2.1 → adjeff-1.2.2}/docs/roadmap.md +0 -0
  18. {adjeff-1.2.1 → adjeff-1.2.2}/notebooks/01-create-and-display-image.ipynb +0 -0
  19. {adjeff-1.2.1 → adjeff-1.2.2}/notebooks/02-atmospheric-configuration.ipynb +0 -0
  20. {adjeff-1.2.1 → adjeff-1.2.2}/notebooks/03-compute-radiative-quantities.ipynb +0 -0
  21. {adjeff-1.2.1 → adjeff-1.2.2}/notebooks/04-simulate-rho-toa.ipynb +0 -0
  22. {adjeff-1.2.1 → adjeff-1.2.2}/notebooks/05-learn-psf.ipynb +0 -0
  23. {adjeff-1.2.1 → adjeff-1.2.2}/notebooks/06-evaluate-a-psf.ipynb +0 -0
  24. {adjeff-1.2.1 → adjeff-1.2.2}/pixi.lock +0 -0
  25. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/__init__.py +0 -0
  26. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/_logging.py +0 -0
  27. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/accessor.py +0 -0
  28. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/analysis/__init__.py +0 -0
  29. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/analysis/_energy.py +0 -0
  30. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/analysis/_error.py +0 -0
  31. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/analysis/_plane.py +0 -0
  32. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/analysis/_radial.py +0 -0
  33. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/analysis/_vectorise.py +0 -0
  34. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/api/__init__.py +0 -0
  35. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/api/_config.py +0 -0
  36. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/api/_load.py +0 -0
  37. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/api/_pipeline.py +0 -0
  38. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/api/_psf.py +0 -0
  39. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/atmosphere/__init__.py +0 -0
  40. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/atmosphere/atmo_config.py +0 -0
  41. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/atmosphere/atmo_factory.py +0 -0
  42. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/atmosphere/geo_config.py +0 -0
  43. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/atmosphere/spectral_config.py +0 -0
  44. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/atmosphere/surface.py +0 -0
  45. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/__init__.py +0 -0
  46. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/_psf.py +0 -0
  47. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/analytical_psf.py +0 -0
  48. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/bands.py +0 -0
  49. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/image_dict.py +0 -0
  50. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/image_generator.py +0 -0
  51. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/non_analytical_psf.py +0 -0
  52. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/core/psf_tree.py +0 -0
  53. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/exceptions.py +0 -0
  54. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/__init__.py +0 -0
  55. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/classic/__init__.py +0 -0
  56. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/classic/toa_to_unif.py +0 -0
  57. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/classic/unif_to_toa.py +0 -0
  58. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/loaders/__init__.py +0 -0
  59. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/loaders/maja_loader.py +0 -0
  60. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/loaders/product_loader.py +0 -0
  61. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/models/__init__.py +0 -0
  62. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/models/psf_conv_module.py +0 -0
  63. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/models/unif2surface.py +0 -0
  64. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/pipeline.py +0 -0
  65. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/__init__.py +0 -0
  66. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/_atmo_sampler.py +0 -0
  67. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/_brdf_sampler.py +0 -0
  68. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/_ensure.py +0 -0
  69. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/_smartg.py +0 -0
  70. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/radiatives.py +0 -0
  71. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/rho_atm.py +0 -0
  72. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/rho_toa.py +0 -0
  73. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/rho_toa_sym.py +0 -0
  74. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/sph_alb.py +0 -0
  75. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/sph_alb_brdf.py +0 -0
  76. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/tdif_down.py +0 -0
  77. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/tdif_up.py +0 -0
  78. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/tdif_up_brdf.py +0 -0
  79. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/tdir_down.py +0 -0
  80. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/samplers/tdir_up.py +0 -0
  81. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/scene_module.py +0 -0
  82. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/modules/scene_source.py +0 -0
  83. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/__init__.py +0 -0
  84. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/_combo_stage.py +0 -0
  85. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/_config.py +0 -0
  86. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/adam_optimizer.py +0 -0
  87. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/fit.py +0 -0
  88. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/landscape.py +0 -0
  89. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/lbfgs_optimizer.py +0 -0
  90. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/loss.py +0 -0
  91. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/metrics.py +0 -0
  92. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/optim/training_set.py +0 -0
  93. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/py.typed +0 -0
  94. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/reference/__init__.py +0 -0
  95. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/reference/wu2024.py +0 -0
  96. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/__init__.py +0 -0
  97. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/_config.py +0 -0
  98. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/cache_store.py +0 -0
  99. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/convolve.py +0 -0
  100. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/radial.py +0 -0
  101. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/smartgutils.py +0 -0
  102. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/torchutils.py +0 -0
  103. {adjeff-1.2.1 → adjeff-1.2.2}/src/adjeff/utils/xrutils.py +0 -0
  104. {adjeff-1.2.1 → adjeff-1.2.2}/tests/_test_module.py +0 -0
  105. {adjeff-1.2.1 → adjeff-1.2.2}/tests/conftest.py +0 -0
  106. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_accessor.py +0 -0
  107. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_analysis.py +0 -0
  108. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_api_docs.py +0 -0
  109. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_api_psf.py +0 -0
  110. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_atmosphere_config.py +0 -0
  111. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_brdf_samplers.py +0 -0
  112. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_classic_modules.py +0 -0
  113. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_config.py +0 -0
  114. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_convolve.py +0 -0
  115. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_image_dict.py +0 -0
  116. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_image_generator.py +0 -0
  117. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_import.py +0 -0
  118. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_integration.py +0 -0
  119. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_landscape.py +0 -0
  120. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_loaders.py +0 -0
  121. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_logging.py +0 -0
  122. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_optim.py +0 -0
  123. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_psf.py +0 -0
  124. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_psf_tree.py +0 -0
  125. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_radial.py +0 -0
  126. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_scene_module.py +0 -0
  127. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_smartg_api.py +0 -0
  128. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_smartg_defaults.py +0 -0
  129. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_smartgutils.py +0 -0
  130. {adjeff-1.2.1 → adjeff-1.2.2}/tests/test_xrutils.py +0 -0
@@ -5,6 +5,16 @@ All notable changes to this project are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [1.2.2]
9
+
10
+ ### Fixed
11
+
12
+ - **The sweep store is named after the scene bound to it.** The scene is
13
+ passed to the physics outside xsweep's fingerprint, so two training
14
+ scenes sweeping the same space shared a store and the second read back
15
+ the first one's results. Introduced in 1.1.0, which is where the store
16
+ comes from.
17
+
8
18
  ## [1.2.1]
9
19
 
10
20
  ### Fixed
@@ -34,9 +44,8 @@ PSF fit being the case that motivated it.
34
44
  - **A cached sweep is written through to zarr and read back lazily.** It
35
45
  applies to the samplers whose output carries the image dims, and only
36
46
  when the module has a cache directory; a sweep returning a spectrum per
37
- state is still assembled in memory. Two consequences: the three training
38
- scenes of a fit share one Smart-G run instead of three, and the result a
39
- module hands on is dask-backed rather than numpy.
47
+ state is still assembled in memory. The result a module hands on is then
48
+ dask-backed rather than numpy.
40
49
  - **`batch_size` controls the batching.** It did not before: xsweep took
41
50
  the whole space in one call whatever its value. Measured on a 3999 pixel
42
51
  grid, a batch of 24 states costs five times the time of three batches of
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: adjeff
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Library that provides utilities to simulate adjacency effects.
5
5
  Project-URL: Homepage, https://github.com/walcark/Adjeff
6
6
  Project-URL: Issues, https://github.com/walcark/Adjeff/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adjeff"
3
- version = "1.2.1"
3
+ version = "1.2.2"
4
4
  authors = [{ name = "walcark", email = "kevin.walcarius@gmail.com" }]
5
5
  readme = "README.md"
6
6
  description = "Library that provides utilities to simulate adjacency effects."
@@ -13,6 +13,7 @@ import functools
13
13
  import hashlib
14
14
  from typing import TYPE_CHECKING, Any, Callable, ClassVar
15
15
 
16
+ import joblib # type: ignore[import-untyped]
16
17
  import numpy as np
17
18
  import xarray as xr
18
19
  from xsweep import Sweeper, SweepPolicy
@@ -134,13 +135,13 @@ class SweepSampler(SceneModule):
134
135
  if spectral is not None:
135
136
  plan["bands"] = len(spectral.bands)
136
137
  self._log.info("sweep.plan", **plan)
137
- result = sweeper(space, policy=self._policy(space), **statics)
138
+ result = sweeper(space, policy=self._policy(space, bound), **statics)
138
139
  out: xr.DataArray = result[self._contract.outputs[0]]
139
140
  return out
140
141
 
141
142
  _SPATIAL: ClassVar[frozenset[str]] = frozenset({"x", "y", "x_psf", "y_psf"})
142
143
 
143
- def _policy(self, space: xr.Dataset) -> SweepPolicy:
144
+ def _policy(self, space: xr.Dataset, bound: dict[str, Any]) -> SweepPolicy:
144
145
  """Return the sweep policy.
145
146
 
146
147
  A sweep whose output carries the image dims is written to zarr as
@@ -154,25 +155,42 @@ class SweepSampler(SceneModule):
154
155
  return SweepPolicy(
155
156
  batch_size=self.batch_size,
156
157
  dedup=self.dedup,
157
- store=str(root / "sweeps" / self._store_name(space)),
158
+ store=str(root / "sweeps" / self._store_name(space, bound)),
158
159
  chunks=self._chunks(space),
159
160
  load=False,
160
161
  )
161
162
 
162
- def _store_name(self, space: xr.Dataset) -> str:
163
- """Return a store directory unique to the sampler, its grid and statics.
163
+ def _store_name(self, space: xr.Dataset, bound: dict[str, Any]) -> str:
164
+ """Return a store directory unique to everything the results depend on.
164
165
 
165
- A store cannot be resumed once anything it was written with has
166
- changed, the aerosol species as much as an axis, so both are part
167
- of its name.
166
+ The axes and the statics, which xsweep fingerprints itself, and the
167
+ scene data bound to the physics, which it does not: two training
168
+ scenes sweep the same space and must not share a store.
168
169
  """
169
170
  axes = {str(d): space[d].values.tolist() for d in space.dims}
170
171
  statics = {k: repr(v) for k, v in self._statics().items()}
171
172
  digest = hashlib.sha1(
172
- repr((sorted(axes.items()), sorted(statics.items()))).encode()
173
+ repr(
174
+ (
175
+ sorted(axes.items()),
176
+ sorted(statics.items()),
177
+ sorted(self._bound_hashes(bound).items()),
178
+ )
179
+ ).encode()
173
180
  ).hexdigest()
174
181
  return f"{type(self).__name__}-{digest[:8]}"
175
182
 
183
+ @staticmethod
184
+ def _bound_hashes(bound: dict[str, Any]) -> dict[str, str]:
185
+ """Return a stable hash of each scene object bound to the physics."""
186
+ hashes: dict[str, str] = {}
187
+ for name, value in bound.items():
188
+ if isinstance(value, (xr.Dataset, xr.DataArray)):
189
+ hashes[name] = str(joblib.hash(value))
190
+ else:
191
+ hashes[name] = repr(value)
192
+ return hashes
193
+
176
194
  def _chunks(self, space: xr.Dataset) -> dict[str, int]:
177
195
  """Return the chunk widths that give batches of ``batch_size`` states.
178
196
 
@@ -197,7 +197,7 @@ class TestSweepPolicy:
197
197
  remove_rayleigh=False,
198
198
  cache=CacheStore(tmp_path),
199
199
  )
200
- policy = sampler._policy(sampler._space())
200
+ policy = sampler._policy(sampler._space(), {})
201
201
  vec = {var.name for var in sampler._contract.vec}
202
202
  assert "RhoToaSymSampler" in str(policy.store)
203
203
  assert set(policy.chunks) <= vec
@@ -225,7 +225,7 @@ class TestSweepPolicy:
225
225
  batch_size=4,
226
226
  cache=CacheStore(tmp_path),
227
227
  )
228
- chunks = sampler._policy(sampler._space()).chunks
228
+ chunks = sampler._policy(sampler._space(), {}).chunks
229
229
  assert np.prod(list(chunks.values())) == 4
230
230
 
231
231
  def test_a_spectral_sweep_is_left_in_memory(self, tmp_path):
@@ -244,7 +244,7 @@ class TestSweepPolicy:
244
244
  remove_rayleigh=False,
245
245
  cache=CacheStore(tmp_path),
246
246
  )
247
- assert sampler._policy(sampler._space()).store is UNSET
247
+ assert sampler._policy(sampler._space(), {}).store is UNSET
248
248
 
249
249
  def test_two_species_do_not_share_a_store(self, tmp_path):
250
250
  from adjeff.modules.samplers import RhoToaSymSampler
@@ -268,6 +268,40 @@ class TestSweepPolicy:
268
268
  remove_rayleigh=False,
269
269
  cache=CacheStore(tmp_path),
270
270
  )
271
- return str(sampler._policy(sampler._space()).store)
271
+ return str(sampler._policy(sampler._space(), {}).store)
272
272
 
273
273
  assert name("ammonium") != name("blackcar")
274
+
275
+ def test_two_scenes_do_not_share_a_store(self, tmp_path):
276
+ from adjeff.core import S2Band, gaussian_image_dict
277
+ from adjeff.modules.samplers import RhoToaSymSampler
278
+ from adjeff.utils import CacheStore
279
+
280
+ sampler = RhoToaSymSampler(
281
+ atmo_config=atmo.AtmoConfig(
282
+ aot=xr.DataArray([0.1, 0.2], dims=["aot"]),
283
+ rh=xr.DataArray([50.0], dims=["rh"]),
284
+ h=xr.DataArray([0.0], dims=["h"]),
285
+ href=xr.DataArray([2.0], dims=["href"]),
286
+ species={"sulphate": 1.0},
287
+ ),
288
+ geo_config=atmo.GeoConfig(
289
+ sza=xr.DataArray([40.0], dims=["sza"]),
290
+ vza=xr.DataArray([0.0], dims=["vza"]),
291
+ saa=xr.DataArray([0.0], dims=["saa"]),
292
+ vaa=xr.DataArray([0.0], dims=["vaa"]),
293
+ ),
294
+ remove_rayleigh=False,
295
+ cache=CacheStore(tmp_path),
296
+ )
297
+ band = S2Band.B02
298
+ scenes = [
299
+ gaussian_image_dict(sigma=s, res_km=0.5, bands=[band], n=9)[band]
300
+ for s in (1.0, 5.0)
301
+ ]
302
+ space = sampler._space()
303
+ stores = [
304
+ str(sampler._policy(space, {"rho_s": ds, "band": band}).store)
305
+ for ds in scenes
306
+ ]
307
+ assert stores[0] != stores[1]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes