morphis 0.10.0__tar.gz → 0.11.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.
- {morphis-0.10.0 → morphis-0.11.0}/PKG-INFO +1 -1
- {morphis-0.10.0 → morphis-0.11.0}/pyproject.toml +1 -1
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/contraction.py +24 -17
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/tests/test_contraction.py +84 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/vector.py +3 -1
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/operator.py +7 -1
- {morphis-0.10.0 → morphis-0.11.0}/README.md +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/_legacy/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/_legacy/coordinates.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/_legacy/rotations.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/_legacy/smoothing.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/_legacy/vectors.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/patterns.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/solvers.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/specs.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/tests/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/tests/test_patterns.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/tests/test_solvers.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/algebra/tests/test_specs.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/config.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/base.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/frame.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/lot_indexed.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/metric.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/multivector.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/operator.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/protocols.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tensor.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tests/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tests/test_complex_blades.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tests/test_maxwell_features.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tests/test_model.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tests/test_operator.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tests/test_operators.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/elements/tests/test_tensor.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/clifford.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/duality.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/exterior.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/operators.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/phasors.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/rotations_3d.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/rotations_4d.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/examples/transforms_pga.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/manifold/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/_helpers.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/duality.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/exponential.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/factorization.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/matrix_rep.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/norms.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/outermorphism.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/products.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/projections.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/spectral.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/structure.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/subspaces.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_complex_operations.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_duality.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_exponential.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_matrix_rep.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_norms.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_operations.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_outermorphism.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_products.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_spectral.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/operations/tests/test_structure.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/topology/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/transforms/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/transforms/actions.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/transforms/projective.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/transforms/rotations.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/transforms/tests/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/transforms/tests/test_projective.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/utils/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/utils/docgen.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/utils/easing.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/utils/exceptions.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/utils/observer.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/utils/pretty.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/canvas.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/contexts.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/drawing/__init__.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/drawing/vectors.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/effects.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/loop.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/operations.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/projection.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/renderer.py +0 -0
- {morphis-0.10.0 → morphis-0.11.0}/src/morphis/visuals/theme.py +0 -0
|
@@ -37,6 +37,8 @@ class IndexedTensor:
|
|
|
37
37
|
Attributes:
|
|
38
38
|
tensor: The underlying Vector or Operator (reference, not copy)
|
|
39
39
|
indices: String of index labels (e.g., "mnab")
|
|
40
|
+
output_geo_indices: Indices that represent output geometric dimensions.
|
|
41
|
+
These determine the result grade when present in contraction output.
|
|
40
42
|
|
|
41
43
|
Examples:
|
|
42
44
|
>>> G = Operator(...) # lot=(M, N), grade=2 output
|
|
@@ -44,18 +46,21 @@ class IndexedTensor:
|
|
|
44
46
|
>>> b = G["mnab"] * q["n"] # contracts on 'n', result has indices "mab"
|
|
45
47
|
"""
|
|
46
48
|
|
|
47
|
-
__slots__ = ("tensor", "indices")
|
|
49
|
+
__slots__ = ("tensor", "indices", "output_geo_indices")
|
|
48
50
|
|
|
49
|
-
def __init__(self, tensor: "Vector | Operator", indices: str):
|
|
51
|
+
def __init__(self, tensor: "Vector | Operator", indices: str, output_geo_indices: str = ""):
|
|
50
52
|
"""
|
|
51
53
|
Create an indexed tensor wrapper.
|
|
52
54
|
|
|
53
55
|
Args:
|
|
54
56
|
tensor: The underlying Vector or Operator
|
|
55
57
|
indices: String of index labels, one per axis of tensor.data
|
|
58
|
+
output_geo_indices: Subset of indices representing output geometric
|
|
59
|
+
dimensions. If empty, will be inferred during contraction (legacy).
|
|
56
60
|
"""
|
|
57
61
|
self.tensor = tensor
|
|
58
62
|
self.indices = indices
|
|
63
|
+
self.output_geo_indices = output_geo_indices
|
|
59
64
|
|
|
60
65
|
# Validate index count matches tensor dimensions
|
|
61
66
|
expected_ndim = tensor.data.ndim
|
|
@@ -78,9 +83,14 @@ class IndexedTensor:
|
|
|
78
83
|
|
|
79
84
|
if isinstance(other, LotIndexed):
|
|
80
85
|
# Convert LotIndexed to IndexedTensor by adding geo indices
|
|
86
|
+
# All of a Vector's geometric indices are "output geometric"
|
|
81
87
|
n_geo = other.vector.grade
|
|
82
88
|
geo_labels = "".join(chr(ord("A") + i) for i in range(n_geo))
|
|
83
|
-
other = IndexedTensor(
|
|
89
|
+
other = IndexedTensor(
|
|
90
|
+
other.vector,
|
|
91
|
+
other.indices + geo_labels,
|
|
92
|
+
output_geo_indices=geo_labels,
|
|
93
|
+
)
|
|
84
94
|
|
|
85
95
|
if not isinstance(other, IndexedTensor):
|
|
86
96
|
return NotImplemented
|
|
@@ -152,23 +162,20 @@ def _contract_indexed(*indexed_tensors: IndexedTensor) -> "Vector":
|
|
|
152
162
|
|
|
153
163
|
|
|
154
164
|
def _infer_grade_from_indexed(indexed_tensors: tuple[IndexedTensor, ...], output_indices: str) -> int:
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# Track which indices are geometric (vs lot)
|
|
159
|
-
geo_indices = set()
|
|
165
|
+
"""
|
|
166
|
+
Infer grade for IndexedTensor contraction result.
|
|
160
167
|
|
|
168
|
+
The result grade equals the number of output geometric indices that appear
|
|
169
|
+
in the result. Each IndexedTensor carries its output_geo_indices, which
|
|
170
|
+
identify which indices represent geometric (grade-contributing) dimensions.
|
|
171
|
+
"""
|
|
172
|
+
# Collect output geometric indices from all tensors
|
|
173
|
+
all_output_geo = set()
|
|
161
174
|
for it in indexed_tensors:
|
|
162
|
-
|
|
163
|
-
n_lot = len(it.tensor.lot)
|
|
164
|
-
n_geo = it.tensor.grade
|
|
165
|
-
# Geometric indices are the last 'grade' indices
|
|
166
|
-
geo_part = it.indices[n_lot : n_lot + n_geo]
|
|
167
|
-
geo_indices.update(geo_part)
|
|
175
|
+
all_output_geo.update(it.output_geo_indices)
|
|
168
176
|
|
|
169
|
-
#
|
|
170
|
-
|
|
171
|
-
return result_grade
|
|
177
|
+
# Result grade = count of output geometric indices in the result
|
|
178
|
+
return sum(1 for idx in output_indices if idx in all_output_geo)
|
|
172
179
|
|
|
173
180
|
|
|
174
181
|
# =============================================================================
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""Tests for tensor contraction with both bracket and einsum-style APIs."""
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
3
4
|
import pytest
|
|
4
5
|
from numpy import array, ones
|
|
5
6
|
from numpy.testing import assert_allclose, assert_array_equal
|
|
6
7
|
|
|
7
8
|
from morphis.algebra.contraction import IndexedTensor, contract
|
|
9
|
+
from morphis.algebra.specs import VectorSpec
|
|
8
10
|
from morphis.elements import Vector, euclidean_metric
|
|
11
|
+
from morphis.operations.operator import Operator
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
# =============================================================================
|
|
@@ -222,3 +225,84 @@ class TestSlicingStillWorks:
|
|
|
222
225
|
|
|
223
226
|
assert isinstance(result, Vector)
|
|
224
227
|
assert result.shape == (5, 3)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# =============================================================================
|
|
231
|
+
# Operator Indexed Contraction
|
|
232
|
+
# =============================================================================
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class TestOperatorIndexedContraction:
|
|
236
|
+
"""Tests for indexed contraction between Operators and Vectors."""
|
|
237
|
+
|
|
238
|
+
def test_operator_vector_contraction_preserves_output_grade(self):
|
|
239
|
+
"""
|
|
240
|
+
Operator contraction should preserve output_spec.grade in result.
|
|
241
|
+
|
|
242
|
+
This is the key regression test: when an Operator with output_spec.grade=2
|
|
243
|
+
contracts with a Vector, the result must have grade=2, not grade=0.
|
|
244
|
+
"""
|
|
245
|
+
g = euclidean_metric(3)
|
|
246
|
+
|
|
247
|
+
# Operator: maps grade-0 lot (N,) to grade-2 lot (M,)
|
|
248
|
+
M, N = 2, 3
|
|
249
|
+
O = Operator(
|
|
250
|
+
data=np.random.randn(M, N, 3, 3), # shape (M, N, 3, 3)
|
|
251
|
+
input_spec=VectorSpec(grade=0, lot=(N,), dim=3),
|
|
252
|
+
output_spec=VectorSpec(grade=2, lot=(M,), dim=3),
|
|
253
|
+
metric=g,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Input: grade-0 with lot (N,)
|
|
257
|
+
v = Vector(np.random.randn(N), grade=0, metric=g)
|
|
258
|
+
|
|
259
|
+
# Contract on "n" (the input lot index)
|
|
260
|
+
result = O["mnab"] * v["n"]
|
|
261
|
+
|
|
262
|
+
# Result should have: grade=2, lot=(M,)
|
|
263
|
+
assert result.grade == 2, f"Expected grade=2, got grade={result.grade}"
|
|
264
|
+
assert result.lot == (M,), f"Expected lot=(2,), got lot={result.lot}"
|
|
265
|
+
assert result.shape == (M, 3, 3)
|
|
266
|
+
|
|
267
|
+
def test_operator_lot_indexed_vector_contraction(self):
|
|
268
|
+
"""Operator contraction via LotIndexed syntax also preserves grade."""
|
|
269
|
+
g = euclidean_metric(3)
|
|
270
|
+
|
|
271
|
+
M, N = 4, 5
|
|
272
|
+
O = Operator(
|
|
273
|
+
data=np.ones((M, N, 3, 3)),
|
|
274
|
+
input_spec=VectorSpec(grade=0, lot=(N,), dim=3),
|
|
275
|
+
output_spec=VectorSpec(grade=2, lot=(M,), dim=3),
|
|
276
|
+
metric=g,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Input as grade-0 vector with lot
|
|
280
|
+
v = Vector(np.ones(N), grade=0, metric=g)
|
|
281
|
+
|
|
282
|
+
result = O["mnab"] * v["n"]
|
|
283
|
+
|
|
284
|
+
assert result.grade == 2
|
|
285
|
+
assert result.lot == (M,)
|
|
286
|
+
# Each element should be sum over N: N * 1 = 5
|
|
287
|
+
assert_allclose(result.data, np.ones((M, 3, 3)) * N)
|
|
288
|
+
|
|
289
|
+
def test_operator_vector_to_vector_contraction(self):
|
|
290
|
+
"""Operator mapping grade-1 to grade-1 preserves grade in contraction."""
|
|
291
|
+
g = euclidean_metric(3)
|
|
292
|
+
|
|
293
|
+
M, N = 2, 3
|
|
294
|
+
# grade-1 -> grade-1 operator (outermorphism)
|
|
295
|
+
O = Operator(
|
|
296
|
+
data=np.eye(3).reshape(1, 1, 3, 3) * np.ones((M, N, 1, 1)),
|
|
297
|
+
input_spec=VectorSpec(grade=1, lot=(N,), dim=3),
|
|
298
|
+
output_spec=VectorSpec(grade=1, lot=(M,), dim=3),
|
|
299
|
+
metric=g,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
v = Vector(np.ones((N, 3)), grade=1, metric=g)
|
|
303
|
+
|
|
304
|
+
result = O["mnab"] * v["nb"]
|
|
305
|
+
|
|
306
|
+
assert result.grade == 1
|
|
307
|
+
assert result.lot == (M,)
|
|
308
|
+
assert result.shape == (M, 3)
|
|
@@ -375,9 +375,11 @@ class Vector(IndexableMixin, Tensor):
|
|
|
375
375
|
return LotIndexed(self, indices)
|
|
376
376
|
elif len(indices) == n_total:
|
|
377
377
|
# Full indexing -> IndexedTensor
|
|
378
|
+
# All geometric indices (after lot) are "output geometric" for Vectors
|
|
378
379
|
from morphis.algebra.contraction import IndexedTensor
|
|
379
380
|
|
|
380
|
-
|
|
381
|
+
output_geo_indices = indices[n_lot:]
|
|
382
|
+
return IndexedTensor(self, indices, output_geo_indices=output_geo_indices)
|
|
381
383
|
else:
|
|
382
384
|
raise ValueError(
|
|
383
385
|
f"Index string '{indices}' has {len(indices)} indices, but expected "
|
|
@@ -249,7 +249,13 @@ class Operator(IndexableMixin):
|
|
|
249
249
|
"""
|
|
250
250
|
from morphis.algebra.contraction import IndexedTensor
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
# Operator layout: (*out_lot, *in_lot, *out_geo, *in_geo)
|
|
253
|
+
# Output geometric indices start after lot dimensions
|
|
254
|
+
lot_end = self.output_spec.collection + self.input_spec.collection
|
|
255
|
+
out_geo_end = lot_end + self.output_spec.grade
|
|
256
|
+
output_geo_indices = indices[lot_end:out_geo_end]
|
|
257
|
+
|
|
258
|
+
return IndexedTensor(self, indices, output_geo_indices=output_geo_indices)
|
|
253
259
|
|
|
254
260
|
def _slice(self, key):
|
|
255
261
|
"""
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|