(defn basis-components->vector-field
"Given a structure of `components` and and a matching `vector-basis` (of
identical structure with orientations flipped), returns a new vector field
generated contracting by these two structures together.
The returned vector field passes its input function to the operator generated
by this contraction.
For example:
```clojure
(let-coordinates [[x y] R2-rect]
(basis-components->vector-field
(up x y)
(coordinate-system->vector-basis R2-rect)))
;; => (+ (* x d:dx) (* y d:dy))
```
NOTE:
- This is for any basis, not just a coordinate basis
- The `components` are evaluated at a manifold point, not its coordinates
- Given a dual basis, you can retrieve the original components
with [[vector-field->basis-components]]"
[components vector-basis]
{:pre [(s/compatible-for-contraction? components
vector-basis)]}
(let [op (fn [f]
(let [applied (vector-basis f)]
(fn [point]
(g/* (applied point)
(components point)))))
name `(~'+ ~@(map (fn [component basis-element]
`(~'*
~(g/freeze component)
~(g/freeze basis-element)))
(flatten components)
(flatten vector-basis)))]
(procedure->vector-field op name)))