Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ S = dg_tensor02sym(dg, randn(200, 6)) # symmetric (0,2)-tensor, (n, d(d+1)
The field types — `ScalarFunction`, `VectorField`, `Form`, `Tensor02`,
`Tensor02Sym`, and `DirectSumElement` — support arithmetic, wedge and tensor
products, `symmetrise`/`transpose_tensor`, and musical isomorphisms `sharp`/`flat`.

```julia
wedge(α, β) # wedge product, degree k₁ + k₂ — also spelled α ^ β
α * β # ⚠ tensor product of two 1-forms → Tensor02, not the wedge
f * α # pointwise product with a function (so is wedge(f, α))
```

The learned metric gives you geometry:

```julia
Expand Down Expand Up @@ -258,6 +265,11 @@ available directly. Animate a time-evolving field with `dganimate`. See
in keeping with Julia. Point-cloud data is `n × d` (rows are points).
- Arrays keep a leading point axis `p`, so a field's coefficients are laid out
per point.
- **`*` on two `Form`s is the tensor product**, following Python; the wedge is
`wedge(α, β)` or `α ^ β`. Julia's `^` binds *tighter* than `+` and `*` where
Python's binds looser than both, so a Python expression that leans on that
precedence changes meaning when copied across: `α ^ β + γ` is `(α ∧ β) + γ`
here, but `α ∧ (β + γ)` in Python. Parenthesise when porting.

## Performance

Expand Down
33 changes: 32 additions & 1 deletion src/tensors/forms/forms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ end
# ── Wedge product α ∧ β : Ωᵏ¹ × Ωᵏ² → Ωᵏ¹⁺ᵏ² ──────────────────────────────────
"""
wedge(a, b) -> Form
a ^ b -> Form

Wedge product of two forms, `(α ∧ β)_K = Σ_{I∪J=K} sgn(I,J) α_I β_J`. Returns a
zero function when the combined degree exceeds the ambient dimension.

`^` is the same product, spelled as in Python. Beware that `*` on two `Form`s is
the *tensor* product, not the wedge.
"""
function wedge(a::Form, b::Form)
dg = geometry(a)
Expand Down Expand Up @@ -77,8 +81,35 @@ function wedge(a::Form, b::Form)
return dg_form(dg, out_pw, ktot)
end

"""
wedge(f::ScalarFunction, ω::Form) -> Form
wedge(ω::Form, f::ScalarFunction) -> Form

A function is a degree-0 form, so the wedge with one degenerates to the pointwise
product (as in Python). There is deliberately no `wedge` on two `ScalarFunction`s:
`f^g` already reads as a pointwise power, and `Base.:^(::ScalarFunction, ::Number)`
is that power — writing `f * g` for the product keeps `^` unambiguous.
"""
wedge(f::ScalarFunction, ω::Form) = f * ω
wedge(ω::Form, f::ScalarFunction) = ω * f

"""
a ^ b -> Form

The wedge product, spelled as in Python. `^` is otherwise unused on `Form`, and
the pointwise power `Base.:^(::ScalarFunction, ::Number)` is a distinct method.
Julia's `^` binds tighter than `+` and `*`, where Python's binds looser than both:
`α ^ β + γ` is `(α ∧ β) + γ` here but `α ∧ (β + γ)` in Python.
"""
Base.:^(a::Form, b::Form) = wedge(a, b)
Base.:^(f::ScalarFunction, ω::Form) = wedge(f, ω)
Base.:^(ω::Form, f::ScalarFunction) = wedge(ω, f)

# ── Tensor product of 1-forms → general (0,2)-tensor ───────────────────────────
"""Tensor product of two 1-forms, `(α ⊗ β)_{ij} = α_i β_j`, giving a `Tensor02`."""
"""
Tensor product of two 1-forms, `(α ⊗ β)_{ij} = α_i β_j`, giving a `Tensor02`.
This is *not* the wedge product — that is `wedge(α, β)`, or `α ^ β`.
"""
function Base.:*(a::Form, b::Form)
dg = geometry(a)
@assert dg === geometry(b) "Forms must be defined on the same DiffusionGeometry."
Expand Down
14 changes: 14 additions & 0 deletions test/test_tensor_algebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@
@testset "wedge and tensor products" begin
@test aeq(wedge(a1, b1).coeffs, fx["wedge_ab_coeffs"])
@test aeq((a1 * b1).coeffs, fx["tensorprod_ab_coeffs"])

# `^` is the Python spelling of the wedge (`*` on two Forms stays the
# tensor product, as in Python).
@test aeq((a1^b1).coeffs, fx["wedge_ab_coeffs"])
@test degree(a1^b1) == 2

# A Function is a degree-0 form: the wedge degenerates to a pointwise
# product, as in Python.
@test aeq(wedge(f, a1).coeffs, (f * a1).coeffs)
@test aeq((f^a1).coeffs, (f * a1).coeffs)
@test aeq((a1^f).coeffs, (a1 * f).coeffs)
# …while `^` on two Functions stays the pointwise power, not a product.
@test aeq((f^2).coeffs, (f * f).coeffs)
@test_throws MethodError wedge(f, h)
end

@testset "symmetrise / expand / transpose" begin
Expand Down
Loading