Skip to content

Conversation

@JoshuaLampert
Copy link
Member

This PR adds another primitive for a parametrized curve $\gamma: [a, b]\to\mathbb{R}^d$ , where the parametrization comes from a user-defined function. Please tell me if I missed any method, which should be added. I'll add tests later after the interface is ironed out.

@codecov
Copy link

codecov bot commented Sep 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 87.44%. Comparing base (994b30a) to head (71da5f8).
Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1093      +/-   ##
==========================================
+ Coverage   87.40%   87.44%   +0.04%     
==========================================
  Files         189      190       +1     
  Lines        5962     5983      +21     
==========================================
+ Hits         5211     5232      +21     
  Misses        751      751              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@juliohm juliohm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate if you can elaborate on the use cases of this primitive so that we can fine tune the best constructors.

If we restrict t in [0,1] and constrain the type to Cartesian parametrizations, we can think of constructing the curve from parametric x(t), y(t) (and possibly z(t)) alone. Otherwise, we can consider other intervals for t and other CRS like Polar, Spherical, Cylindrical coordinates in which case, the parametrizations would be r(t), theta(t), etc.

@JoshuaLampert
Copy link
Member Author

Thanks a lot for your feedback @juliohm. I structure your comments into two points:

  1. The parameter range: I see that always having $t\in[0, 1]$ is simple, convenient for the implementation, and often also the standard. However, often I also find a curve defined on a general interval $[a, b]$ (I think I've also learned it that way at university or see e.g. https://en.wikipedia.org/wiki/Line_integral or also https://en.wikipedia.org/wiki/Curve). Of course, you can always simply transform every interval $[a, b]$ into $[0, 1]$ pretty easily, but it adds an inconvenience if one happens to have a curve on another interval, which one wants to define quickly without thinking of a transformation first. One alternative to simply removing the general start and end parameter I thought of is that evaluating the curve is only defined for $t\in[0,1]$, i.e. curve(0.0) would give curve.func(a) for instance. In other words we perform the transformation internally. On the other hand this is also not optimal as it might be confusing that you provide start and end parameters for the parameter range, but in the end the curve can still only be evaluated in $[0, 1]$. If you also don't see any better option, I would be fine with both versions, which means I let you decide.
  2. The CRS: I do plan to use other CRS than Cartesian, see also my use case I have in mind below. Thus, I would like the user to explicitly create a Point, such that we know the CRS. Having said that, we can of course think of adding another convenience constructor taking functions x(t), y(t) and potentially z(t), which internally creates a Point in Cartesian coordinates by default.

One use case I have in mind is the following. I would like to compute integrals along non-trivial curves in 2D like the one in Figure 7 of https://academic.oup.com/imajna/article/43/3/1616/6586010. Looking at the source code of this paper the curve can be described by the following curve in polar coordinates: $\gamma: [-\pi, \pi] \to\mathbb{R}^2, t\mapsto [\rho(t), \phi(t)]^T = [1 - \frac{1}{3} \sin(2t)^2, t]^T$. It would be very nice if this could simply be implemented as

f(theta) = 1 - 1/3 * sin(2*theta)^2
curve = ParametrizedCurve(-pi, pi, t  -> Point(Polar(f(t), t)))

The following already works with the current implementation, which is good (but not as nice as the above, see point 1):

curve = ParametrizedCurve(0.0, 1.0, t -> Point(Polar(f(2pi * t - pi), 2pi * t - pi)))

E.g. viz(curve) shows
curve

@mikeingold
Copy link
Contributor

  1. One alternative to simply removing the general start and end parameter I thought of is that evaluating the curve is only defined for $t ∈ [ 0, 1]$ i.e. curve(0.0) would give curve.func(a) for instance. In other words we perform the transformation internally. On the other hand this is also not optimal as it might be confusing that you provide start and end parameters for the parameter range, but in the end the curve can still only be evaluated in $[0,1]$. If you also don't see any better option, I would be fine with both versions, which means I let you decide.

This is a tricky question. All of Meshes.jl's current parametric functions, and other features that utilize them, assume an interface with domain $[0,1]^n$. However, I think it would be more user-friendly to allow arbitrary end-points, i.e $u = [a, b]$.

If we maintained support for curve(t) where $t ∈ [ 0, 1]$ but translated this internally to use another domain, i.e. $u = [a, b]$ this could somewhat bridge the gap. Maybe we just need another way to call the curve directly with u coordinates? Brainstorming:

  • Maybe a new function, e.g. parametric(curve, u)
  • Maybe dispatch differently, e.g. curve(u, ::Symbol)?

@juliohm
Copy link
Member

juliohm commented Oct 1, 2024

The current approach where the interval [a,b] is handled internally in the struct is flexible and doesn't compromise the assumption that uv coords are unitless coordinates in the range [0,1]. Perhaps we just need to make this interval [a,b] something of secondary importance with [0,1] as the default. What about the following constructor:

ParametrizedCurve(f, ab=(0.0, 1.0))

That way you could write the example as

f(theta) = 1 - 1/3 * sin(2*theta)^2

# explicit ab interval
ParametrizedCurve(t  -> Point(Polar(f(t), t)), (-pi, pi))

# default interval
ParametrizedCurve(t -> Point(Polar(f(2pi * t - pi), 2pi * t - pi)))

@JoshuaLampert
Copy link
Member Author

Sounds good @juliohm. I pushed your suggestion.

Copy link
Member

@juliohm juliohm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few minor suggestions to consider before we start adding tests and docs.

@mikeingold
Copy link
Contributor

mikeingold commented Oct 1, 2024 via email

@JoshuaLampert
Copy link
Member Author

I'll address your suggestions once I am back at my workstation @juliohm.
Thanks for starting the discussion about higher dimensional user-supplied parametric functions @mikeingold. I also thought about that, but assumed we would need different types for that. So I wanted to postpone that for now. But you might be right that it could be good to just have the same struct for all parametric dimensions, which means we should discuss it already in this PR to avoid breaking changes. I would be interested in your thoughts, @juliohm.

@mikeingold
Copy link
Contributor

mikeingold commented Oct 1, 2024 via email

@juliohm
Copy link
Member

juliohm commented Oct 2, 2024

If we want to define a one-dimensional geometry defined by user-supplied parametric functions, it seems logical that we might consider extending this in the future to also accommodate parametric surfaces and volumes. If we were to go that far, would we need to define new types, e.g. ParametricSurface, or could these all fall under the same basic type, e.g. Box?

Very good point. I wonder what is the best design here. We are trying to achieve a continuous representation with any number of parametric coordinates func = (u, v, w, ...) -> Point(f(u, v, w...), g(u, v, w...), h(u, v, w...)). The number of parameters determines the parametric dimension as usual (1 = curve, 2 = surface, 3 = volume). The only thing that is unique about func is that it must return a Point. Other than that, it is a normal Julia Function.

If we think of the ranges of the parameters (u, v, w...) as sides of a Box, then we can interpret the Function as a mapping from a n-dimensional Box to a n-dimensional shape. TransformedGeometry(geom, transform) is a related concept that stores the result of the transform on the geom lazily. Transforms are not the same thing as functions though.

@juliohm
Copy link
Member

juliohm commented Oct 2, 2024

I think I have an idea. The TransformedGeometry handles the lazy transformation of a simpler geometry. We can achieve a representation of curves, surfaces etc by mapping Box with a new Morphological(func) (or similar name) transform. The func in this case has a simpler form, it takes a coordinates object and returns another coordinates object:

function f(c::Cartesian)
  x, y = c.x, c.y
  Polar(x, x / y)
end

The Morphological coordinate transform will simply wrap the result into a new Point. After we have this general machinery to convert Box into other n-dimensional shapes, we can think of syntax sugar and helper functions to facilitate the creation of TransformedGeometry(geom, Morphological(func)).

Maybe that will cover the use cases you have in mind? Should we experiment with the Morphological transform in a separate PR and come back here later?

@JoshuaLampert
Copy link
Member Author

Sounds interesting. I'll need to think about it and experiment a bit (I'm currently on vacation, so I might need a bit of time).

@JoshuaLampert JoshuaLampert mentioned this pull request Oct 7, 2024
3 tasks
@JoshuaLampert
Copy link
Member Author

In #1100 we came to the conclusion that having a new Morphological transform is not suitable to tackle this problem. Thus, let's come back to adding a new primitive as proposed in this PR. I am still wondering whether it makes sense to have a ParametrizedCurve type and then possible also define ParametrizedSurface and ParametrizedVolume types later or if we could define a new primitive, which is dimension independent (something like ParametrizedGeometry). This means the user can provide a function taking any number of parameters, which defines the parametrization of the resulting geometry. While many aspects of this PR are probably generalizable to arbitrary dimensions, I'm not sure everything is so straightforward. I am specifically thinking of a general version of boundary. I'm not sure how to define that for a general unknown parametrization in unknown dimension. If you think it would be possible to define something like this, I think a general version would be nice. Otherwise if it is too hard, I would opt for defining ParametrizedCurve for 1D etc.

@juliohm
Copy link
Member

juliohm commented Oct 8, 2024

Thank you @JoshuaLampert, these are great additions.

I will try to find some time to write a section in the docs regarding the uv coords of geometries.

@JoshuaLampert
Copy link
Member Author

That would be great! Thanks a lot for your valuable input and review.

@JoshuaLampert
Copy link
Member Author

I added tests. The only test failure seems unrelated to this PR.

@JoshuaLampert JoshuaLampert requested a review from juliohm October 9, 2024 15:00
Copy link
Member

@juliohm juliohm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor adjustments regarding machine type of literals.

@juliohm juliohm requested a review from eliascarv October 9, 2024 16:39
Co-authored-by: Júlio Hoffimann <julio.hoffimann@gmail.com>
JoshuaLampert and others added 2 commits October 9, 2024 19:23
Co-authored-by: Elias Carvalho <73039601+eliascarv@users.noreply.github.com>
@juliohm juliohm merged commit fe86b2e into JuliaGeometry:master Oct 9, 2024
@juliohm
Copy link
Member

juliohm commented Oct 9, 2024

Thank you @JoshuaLampert ! Releasing a patch...

@JoshuaLampert JoshuaLampert deleted the parametrized-curve branch October 9, 2024 17:46
@JoshuaLampert
Copy link
Member Author

JoshuaLampert commented Oct 12, 2024

I will try to find some time to write a section in the docs regarding the uv coords of geometries.

Now I see it's documented in https://juliageometry.github.io/MeshesDocs/stable/predicates/#Meshes.isparametrized, but a more prominent documentation could still be helpful (but is not urgent).
Note that it is also not always exactly true that every parametrized geometry can be called with local coordinates in [0,1]^n. Exceptions are Triangle and Tetrahedron (you have the additional restriction of $u + v (+ w) \le 1$) and Line, Plane, and Ray, where some of the arguments can go to infinity in one or both directions.

souma4 pushed a commit to souma4/Meshes.jl that referenced this pull request Feb 11, 2025
* add ParametrizedCurve

* include file

* add docstring

* format

* remove underscores in names

* use minimum and maximum

* fix

* interval ab as optional argument

* format

* move include

* Apply suggestions from code review

Co-authored-by: Júlio Hoffimann <julio.hoffimann@gmail.com>

* restructure range

* Apply suggestions from code review

Co-authored-by: Júlio Hoffimann <julio.hoffimann@gmail.com>

* promote range

* Update src/geometries/primitives/parametrizedcurve.jl

* Update src/geometries/primitives/parametrizedcurve.jl

* Update src/geometries/primitives/parametrizedcurve.jl

Co-authored-by: Joshua Lampert <51029046+JoshuaLampert@users.noreply.github.com>

* add docs

* Update src/geometries/primitives/parametrizedcurve.jl

Co-authored-by: Júlio Hoffimann <julio.hoffimann@gmail.com>

* remove newline

* fix Float32 tests

* add predicates test

* add crs test

* add sampling test

* add discretization tests

* format

* use cart and merc consistently

* Apply suggestions from code review

Co-authored-by: Júlio Hoffimann <julio.hoffimann@gmail.com>

* Update src/geometries/primitives/parametrizedcurve.jl

Co-authored-by: Elias Carvalho <73039601+eliascarv@users.noreply.github.com>

* func -> fun

---------

Co-authored-by: Júlio Hoffimann <julio.hoffimann@gmail.com>
Co-authored-by: Elias Carvalho <73039601+eliascarv@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants