ohno / Antique.jl

Self-contained, well-tested, well-documented Analytical Solutions of Quantum Mechanical Equations.
https://ohno.github.io/Antique.jl/
MIT License
18 stars 3 forks source link

Dependent Package of Morse Potential #3

Closed ohno closed 6 months ago

ohno commented 7 months ago

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.
hyrodium commented 7 months ago

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

ohno commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

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 commented 6 months ago

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:

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 commented 6 months ago

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.

ohno commented 6 months ago

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:

Both of my answers are yes.

hyrodium commented 6 months ago

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:

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 commented 6 months ago

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 commented 6 months ago

You can just close this issue and open a new issue :smile: