Skip to content

Conversation

yosrabi
Copy link
Collaborator

@yosrabi yosrabi commented Sep 26, 2025

Hi, After some testing I think this is working as intended. All feedback and improvement ideas are welcome.

Copy link
Collaborator

@mtsch mtsch left a comment

Choose a reason for hiding this comment

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

Looks great! I haven't tested the code yet, but I didn't notice any potential issues, so I only have some style comments for now. Generally, we try to adhere to something close to this style guide https://github.com/JuliaDiff/BlueStyle

The PR also needs some tests. Add a few examples of interface tests (see first test set in test/Hamiltonians.jl). Other things you could test for is that this code is equivalent to FroelichPolaron for 1D and some general property tests (you can use the FroelichPolaron tests for inspiration there).

function FroehlichPolaron_nD(
address::OccupationNumberFS{M,AT};
D::Int = 2,
geometry::CubicGrid = PeriodicBoundaries(ntuple(_ -> Int(round(num_modes(address)^(1/D))), D)),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
geometry::CubicGrid = PeriodicBoundaries(ntuple(_ -> Int(round(num_modes(address)^(1/D))), D)),
geometry::CubicGrid = PeriodicBoundaries(ntuple(Returns(round(Int, M^(1/D))), D)),

A bit more compact, but equivalent

) where {M, AT}

# Check positive l
if length(l) != D || any(x -> x <= 0, l)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Feel free to ignore this comment, but I would write it like this

Suggested change
if length(l) != D || any(x -> x <= 0, l)
if length(l) != D || any(<=(0), l)

end

#check m_lin is possible
if abs(M^(1/D) - round(M^(1/D))) >0.01
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if abs(M^(1/D) - round(M^(1/D))) >0.01
if abs(M^(1/D) - round(M^(1/D))) > 0.01

Try to put spaces on both sides of operators, except in very long expressions where having some terms close together makes it more readable.

Comment on lines 56 to 63
for idx_mode in 1:M
idx = Tuple(geometry[idx_mode])
kv = ntuple(d -> begin
m = idx[d] - 1 - div(round(M^(1/D)),2)
(2π / l[d]) * m
end, D)
ks_tmp[idx_mode] = SVector{D,Float64}(kv)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
for idx_mode in 1:M
idx = Tuple(geometry[idx_mode])
kv = ntuple(d -> begin
m = idx[d] - 1 - div(round(M^(1/D)),2)
(2π / l[d]) * m
end, D)
ks_tmp[idx_mode] = SVector{D,Float64}(kv)
end
for idx_mode in 1:M
idx = Tuple(geometry[idx_mode])
ntuple(D) do d
m = idx[d] - 1 - div(round(M^(1/D)), 2)
(2π / l[d]) * m
end
ks_tmp[idx_mode] = SVector{D,Float64}(kv)
end

Please make sure the code is indented properly! The do notation is also helpful here.

Comment on lines 107 to 108
Pph = zero(h.ks[1])
Nph = 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not clear what these are. Try to use more descriptive variable names.

using SpecialFunctions

#structure
struct FroehlichPolaron_nD{
Copy link
Collaborator

Choose a reason for hiding this comment

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

We usually avoid underscores in struct names. Maybe FroelichPolaronND?

Since this supports 1D as well, do we still need the regular FroelichPolaron? If this is just as efficient and can be used as a drop-in replacement, I think the old Hamiltonian should be removed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks, @yosrabi for the PR! The code looks nice and I'm looking forward to having the multi-dimensional Froehlich model represented in Rimu.

For now I have just a few comments supporting what @mtsch has already said:

It is common in Julia to use 'CamelCase' for structs and 'snake_case' for functions, and we follow this convention (you can google the terms).

I would also strongly prefer to not have two different Froehlich polaron Hamiltonians in Rimu, in particular if they have intersecting functionality. What I had in mind for the extension to 2 and 3 dimensions was an implementation that just adds new features to the existing FroehlichPolaron by adding new keyword arguments (e.g. for dimension with default set to 1) and reusing the old code, user interface, and tests where possible and extending or replacing them only where necessary. Please consider whether this is still feasible! Otherwise we could go with Matija's suggestion to let this new implementation supersede the old one. This would make sense if the new code was faster/more efficient/more elegant or in some other way superior to the old one. As a minimum it should not be any worse.

Ideally, the call signature should be compatible such that replacing the old with the new Hamiltonian would be a non-breaking change (i.e. all previous scripts making use of the documented features of the old FroehlichPolaron would still run without errors). If there is a good reason to change the behaviour or call signature such that a breaking change is required, we can discuss that as well, of course.

Adding tests for the new code and features that run automatically is important (and a hard requirement for any new code to be added to the codebase). These test are all in the test/ folder. The entry point is test/runtests.jl but your test should be added to test/Hamiltonians.jl. I suggest that you look into that file and search for FroelichPolaron to see what tests are already being done for the existing implementation. Ideally, the tests should run on the smallest units (functions) of the new code, check for the correctness of the results, and cover all (feasible) use cases. The minimum requirement is that all (meaningful) parts of the new code are run at least once during the tests.

Finally, I see that GitHub has detected conflicts of your branch with the main develop branch. The reason for this to happen is likely that we recently merged a few other PRs that may be conflicting with yours. The best way to deal with this is to (first fetch and then) merge develop into your branch locally on your computer, and resolve any merge conflicts with a code editor (e.g. vscode) before committing the changes and pushing the local branch back to the repo. There should not be any serious merge conficts as you've mostly edited a new file. You'll simplify your life a lot if you do this before acting on any other comments from this code review.

Comment on lines 118 to 121
function operator_column(h::FroehlichPolaron_nD, address)
M = num_modes(address)
return FroehlichPolaron_nDColumn(h, address, 2M)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would move this function next to the struct definition since it acts like a constructor.

offdiagonals(col::FroehlichPolaron_nDColumn) = FroehlichPolaron_nDOffdiagonals(col.address, col.hamiltonian, col.num_offdiagonals)

Base.size(ods::FroehlichPolaron_nDOffdiagonals) = (ods.num_offdiagonals,)
Base.eltype(::FroehlichPolaron_nDOffdiagonals{A}) where {A} = Pair{A,Float64}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not necessary, a default definition is created when you make something a subtype of AbstractVector.

end


#diagonal elements calculator
Copy link
Collaborator

Choose a reason for hiding this comment

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

Try avoiding comments that simply state what the code does. They should be used to explain how/why something is done in a particular way or to break up long chunks of code.

Comment on lines 135 to 137
#calculate V_k for a specific k. Edge case of D = 1.


Copy link
Collaborator

Choose a reason for hiding this comment

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

Here, a docstring would be more useful than a comment.

@yosrabi yosrabi requested a review from mtsch October 14, 2025 09:16
@yosrabi
Copy link
Collaborator Author

yosrabi commented Oct 14, 2025

Sorry i think I accidentally tried to merge the branch. The requested changes have been implemented and the tests updated.

Copy link
Collaborator

@jamie-tay jamie-tay left a comment

Choose a reason for hiding this comment

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

This is looking great! Just a few small comments.

Comment on lines +25 to +28
function operator_column(h::FroehlichPolaronND, address)
M = num_modes(address)
return FroehlichPolaronNDColumn(h, address, 2M)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be under the definition of the FroehlichPolaronNDColumn struct.

Comment on lines +220 to +223
function Base.iterate(ods::FroehlichPolaronNDOffdiagonals, state::Int=1)
state > ods.num_offdiagonals && return nothing
return ods[state], state + 1
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not needed, since the offdiagonals struct is an AbstractVector.

end


if h.momentum_cutoff != nothing
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if h.momentum_cutoff != nothing
if !isnothing(h.momentum_cutoff)

This is slightly more efficient.

Comment on lines +162 to +174
@inline function calc_vk(h::FroehlichPolaronND, kidx::Int)
knorm = sqrt(dot(h.ks[kidx], h.ks[kidx]))
vol = prod(h.l)
if h.d == 1
return (2 * h.alpha /(h.l[1]))^0.5
else
if knorm == 0.0
return 0.0
else
return (-1) * sqrt((gamma(((h.d-1)/2)) * h.alpha * 2^(h.d-(3/2)) * pi^((h.d-1)/2) * h.omega^2) / (sqrt(h.mass * h.omega) * (knorm^(h.d-1) * vol)))
end
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Most of this doesn't need to be calculated over and over again; I'd suggest moving the constant part into the Hamiltonian as h.v, and then this function simplifies to something like

@inline function calc_vk(h::FroehlichPolaronND, kidx::Int)
    if h.d == 1
        return h.v
    else
        knorm = sqrt(dot(h.ks[kidx], h.ks[kidx]))
        if knorm == 0.0
            return 0.0
        else
            return h.v / sqrt(knorm^(h.d - 1))
        end
    end
end

Comment on lines +50 to +52
if abs(M^(1/D) - round(M^(1/D))) > 0.01
throw(ArgumentError("M = $M is not a perfect $D th power"))
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if abs(M^(1/D) - round(M^(1/D))) > 0.01
throw(ArgumentError("M = $M is not a perfect $D th power"))
end
if D > 1 && !isinteger(log(D, M))
throw(ArgumentError("M = $M is not a perfect power of $D"))
end

momentum(HubbardMom1D(BoseFS(0, 1, 5, 1, 0))),
Rimu.FirstOrderTransitionOperator(HubbardRealSpace(BoseFS(1,1,1,1)), -5.0, 0.01),
HubbardReal1D(BoseFS(2,0,0); u=1.0im) * ExtendedHubbardReal1D(BoseFS(2,0,0)),
FroehlichPolaronND(OccupationNumberFS(0,0,0,0))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
FroehlichPolaronND(OccupationNumberFS(0,0,0,0))
FroehlichPolaronND(OccupationNumberFS(0,0,0,0)),

@test get_offdiagonal(f6, addr5, 1) == get_offdiagonal(f7, addr5, 1)
end

@testset "FroehlichPolaronND" begin
Copy link
Collaborator

Choose a reason for hiding this comment

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

These tests look good, we should probably have some checking that it works as expected for higher dimensions as well.

end


function Base.show(io::IO, h::FroehlichPolaronND)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The result of this should be able to be pasted back into the REPL.

@@ -0,0 +1,235 @@
using SpecialFunctions
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is already done in Hamiltonians.jl, so we don't need it here.

using SpecialFunctions


struct FroehlichPolaronND{
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should have a docstring; see the other Hamiltonians for examples.

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