Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependent Package of Morse Potential #3

Closed
ohno opened this issue Dec 10, 2023 · 10 comments
Closed

Dependent Package of Morse Potential #3

ohno opened this issue Dec 10, 2023 · 10 comments

Comments

@ohno
Copy link
Owner

ohno commented Dec 10, 2023

In:

using Antique
MP = antique(:MorsePotential)

Out:

ArgumentError: Package SpecialFunctions not found in current path.
- Run `import Pkg; Pkg.add("SpecialFunctions")` to install the SpecialFunctions package.
ohno added a commit that referenced this issue Dec 10, 2023
@hyrodium
Copy link
Collaborator

Is there any reason not to add a dependency on SpecialFunctions.jl?

@ohno
Copy link
Owner Author

ohno commented Dec 11, 2023

Thank you for your question. No, there is not. I have already added it (Please check Project.toml. Is it correct?). The problem is that antique() generates a module outside Antique via metaprogramming.

julia> using Antique

julia> MP1 = Antique.MorsePotential
Antique.MorsePotential

julia> MP2 = antique(:MorsePotential)
Base.Meta.MorsePotential1669411154543485985

julia> parentmodule(MP1)
Antique

julia> parentmodule(MP2)
Base.Meta

The module MP2 cannot access SpecialFunctions.jl that is as the depentency of Antique.jl. I should find the way to generate a module inside Antique. It might be able to do that by evaluating include(). I am looking for the way to read the string directly with include() because I do not want to generate a new file.

@hyrodium
Copy link
Collaborator

Hm, I think we don't need metaprogramming for the features in this package.
For example, InfinitePotentialWell can be implemented as a struct, not a module, something like this:

abstract type Model end
@kwdef struct InfinitePotentialWell{T<:Real} <: Model
    L::T = 1.0
    m::T = 1.0::T = 1.0
end

# Potential
function V(model::InfinitePotentialWell{T}, x) where T
    L = model.L
    return 0<x<L ? zero(float(T)) : T(Inf)
end
# Wave Function
function ψ(model::InfinitePotentialWell{T}, x; n=1) where T
    L = model.L
    return 0<x<L ? sqrt(2/L) * sin(n*π*x/L) : zero(float(T))
end
# Energy
function E(model::InfinitePotentialWell; n=1)
    m = model.m
    L = model.L
    ℏ = model.return (ℏ^2*n^2*π^2) / (2*m*L^2)
end

@hyrodium
Copy link
Collaborator

I have already added it (Please check Project.toml. Is it correct?)

Thanks for the clarification! The [deps] table is correct, and the best approach would be avoiding metaprogramming, I guess.
Btw, I think Revise.jl in [weakdeps] can be removed.

ohno added a commit that referenced this issue Dec 14, 2023
@ohno
Copy link
Owner Author

ohno commented Dec 15, 2023

Thank you for your suggestions. It is good example for me to learn Julia coding. I agree that your suggestion is the correct approach for Julia programming based on multiple dispatch. But I avoided this coding style for some reasons:

  • Multiple dispatch may reduce the portability to Fortran, Phython or other languages.
  • The current modules are more simple and more similar to the mathematical notation.
  • A "model" does not mean just a set or a tuplet of paramters. A model must have a definition of a Hamiltonian or a Lagrangian. For example, the functional form of the potential energy $V(x)$ is necessary to identify the system. Following this philosophy, a model should be provided as a class or a module.
  • I want to keep functions in one module to use @autodocs. (I am planning to port docstrings from docs/src/*.md.)
  • ψ(x) and E() are common in currently supported models but not in the future. (I will accept the Schrodinger equation as well as the Dirac equation and other quantum mechanical equations or models.) It seems be better that each model is concluded into one module for coexistence of models from different theoretical frameworks.

For the time being, this problem has been solved by replacing Meta.eval(Meta.parse(source)) with Base.include_string(Antique, source). The following code is currently working.

using Pkg
Pkg.rm("SpecialFunctions")
Pkg.rm("Antique")
Pkg.add(url="https://github.com/ohno/Antique.jl.git")
using Antique
MP = antique(:MorsePotential)

And also thank you for your commnets about Project.toml. I will update it.

@hyrodium
Copy link
Collaborator

Multiple dispatch may reduce the portability to Fortran, Phython or other languages.

I think we currently don't have a plan to migrate to other languages, right?
Multiple dispatches feature is the core of Julia, and it should not be avoided. (Do you want to write your Python code like begin=0; li=[4,3,2]; li[begin+1] for portability to Julia? I think that is not pythonic and also should be avoided.)

Following this philosophy, a model should be provided as a class or a module.

Julia does not have class because struct and multiple dispatch can replace class. A class owns methods, but Julia's struct does not own methods. This is a big difference, but that does not produce practical problems at least in the topic in this issue, I guess.

We can still use just module without Base.include_string, and that will be another design choice.

I want to keep functions in one module to use @autodocs. (I am planning to port docstrings from docs/src/*.md.)

I don't quite understand this point. There will be no problems with @autodocs with multiple dispatches, right?

For the time being, this problem has been solved by replacing Meta.eval(Meta.parse(source)) with Base.include_string(Antique, source). The following code is currently working.

The dynamic code loading is not recommended for the following reasons.

  • It cannot be precompiled.
  • It cannot cooperate with some static analysis tools such as JET.jl
  • Replacing values with regex is some kind of 牛刀割鶏.

@ohno
Copy link
Owner Author

ohno commented Dec 16, 2023

I think we currently don't have a plan to migrate to other languages, right? Multiple dispatches feature is the core of Julia, and it should not be avoided. (Do you want to write your Python code like begin=0; li=[4,3,2]; li[begin+1] for portability to Julia? I think that is not pythonic and also should be avoided.)

Right. There are no definite plans. However, I am sure that someone (including me) will migrate to other languages because analytical solutions are important as benchmarks regardless of language. It is better that codes can be easily migrate to other language (especially Fortran, because I am often forced to use Fortran). I aimed for each module to be the reference implementation independent from the language.

Actually, the portability it is just a bonus. The highest priority is on similarity between the code and the formulae. Because the language of physics is mathematical formulae. Programs should mimic mathematical formulae as much as possible. Other languages could not satisfy it. This is my request to Julia as a greedy Julia user. Be mathematical formulae!

Julia does not have class because struct and multiple dispatch can replace class. A class owns methods, but Julia's struct does not own methods.

I want to use class. The function antique() creates a new module instead of creating a new instance of a class. This trick satisfied my requirements. (A struct does not satisfy the above-mentioned philosophy because it does not own methods.)

There will be no problems with @autodocs with multiple dispatches, right?

Yes. I meant that I multi-dispatch was not necessary if used within the scope of a module (I did not have enough words). Regardless of using multi-dispatch, functions will be concluded in one module for each model to help using @autodocs.

The dynamic code loading is not recommended for the following reasons.

  • It cannot be precompiled.
  • It cannot cooperate with some static analysis tools such as JET.jl
  • Replacing values with regex is some kind of 牛刀割鶏.

This comment has some new points of view for me. I offer one compromise to this problem. antique(:model) is not precompiled but Antique.model is precompiled. We can still use just module (Antique.model) without Base.include_string to avoid above problems. In this case, new instances cannot be created. Also, there are some way to avoid regex if we do not mind sone double efforts (for exampe, making a source code generator by replacing ℏ = 1.0 # change here! with ℏ = $ℏ).

My key issues are following two about design philosophy:

  • Does the similarity between mathematical formulae and codes have the highest priority?
  • Should a "model" own methods?

Both of my answers are yes.

@hyrodium
Copy link
Collaborator

Does the similarity between mathematical formulae and codes have the highest priority?

IMO, mathematical coding is good for readability and has high priority only inside functions, however, other parts such as API (exported function names, type names, field names etc.) should not be mathematical symbols.
Here are some reasons for that:

  • Mathematical symbols tend to be short (like just U), and that may easily conflict with other packages.
  • API should be descriptive. For example, internal_energy is better than U for easy understanding.
  • Some package users may want to use different symbols, like V instead of U. One can import import OtherPkg.U as V, but this is still confusing.

Should a "model" own methods?

As I said before, a "model" does not have to own methods. Multiple dispatches can handle it.

One good example is Random.AbstractRNG (supertype for random number generators). It is a "generator", but it does not "own" generating methods.

julia> using Random

julia> rng = Random.MersenneTwister(42)
MersenneTwister(42)

julia> rng isa AbstractRNG
true

julia> rand(rng)
0.5331830160438613

Of course in Python, numpy.Generator own random method.

In [1]: import numpy as np

In [2]: rng = np.random.default_rng(42)

In [3]: rng.random()
Out[3]: 0.7739560485559633

@ohno ohno changed the title Dependent Package of Morse Potential Programming paradigm & style Dec 30, 2023
@ohno
Copy link
Owner Author

ohno commented Dec 30, 2023

The initial problem has been resolved. We are discussing no longer the initial issue. I changed the title of issue from "Dependent Package of Morse Potential" to "Programming paradigm & style" for saving the logs. I will postpone the dicision about your points because there are currently no major problems.

@hyrodium
Copy link
Collaborator

You can just close this issue and open a new issue 😄

@ohno ohno changed the title Programming paradigm & style Dependent Package of Morse Potential Dec 30, 2023
@ohno ohno closed this as completed Dec 30, 2023
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

No branches or pull requests

2 participants