Interfaces.jl unable to find implementation

Hi.

I am trying to implement Interfaces.jl for my package.

The code can be found here: SolarPosition.jl/src at main · PVSMC/SolarPosition.jl · GitHub

The interface definition is here: SolarPosition.jl/src/PositionInterface/PositionInterface.jl at main · PVSMC/SolarPosition.jl · GitHub

module PositionInterface

using Interfaces
using DynamicQuantities: Quantity

export SolarPositionInterface, SolarPositionAlgorithm

abstract type SolarPositionAlgorithm end

function sunpos end

components = (
    mandatory = (
        sunpos_check = (
        "interface implements the sunpos function" => a::Arguments -> sunpos(
        a.algorithm, a.timestamp) isa Tuple{Quantity, Quantity}
    ),
    ),
    optional = ()
)

description = """
Defines an interface to calculate the position of the sun in the sky from a given 
observer position and time defined by a location and timestamp. 

A solar position is uniquely defined by the elevation and azimuth angles. 
"""

@interface SolarPositionInterface SolarPositionAlgorithm components description

end # module PositionInterface

The test I implemented is here: SolarPosition.jl/test/test-interfaces.jl at main · PVSMC/SolarPosition.jl · GitHub

using TimeZones: now, TimeZone
using Interfaces

using PVSimBase.GeoLocation: Location
using SolarPosition.Basic
using SolarPosition.PositionInterface

@testset "Basic algorithm" begin
    test_location = Location(latitude = 0.0, longitude = 0.0)
    alg = BasicAlgorithm(test_location)
    @test alg isa BasicAlgorithm
    @test alg.location == test_location
    pos = sunpos(alg, now(TimeZone("UTC")))
end

@testset "PositionInterfaces" begin
    @test Interfaces.test()
end

I want every SolarPositionAlgorithm to conform to the SolarPositionInterface interface (some parts are mandatory, others optional).

@test Interfaces.test() throws this error:

Testing SolarPositionInterface is implemented for BasicAlgorithm
InterfaceError: test for SolarPositionInterface :sunpos_check "interface implements the sunpos function" threw a MethodError 
 For test object Arguments{(:algorithm, :timestamp), Tuple{BasicAlgorithm, TimeZones.ZonedDateTime}}((algorithm = BasicAlgorithm(Location(0.0 , 0.0 , 0.0 , tz"UTC")), timestamp = TimeZones.ZonedDateTime(2024, 12, 31, 2, 33, 26, 462, tz"UTC"))):

PositionInterfaces: Error During Test at /Projects/2024/PV/SolarPosition.jl/test/test-interfaces.jl:17
  Test threw exception
  Expression: Interfaces.test()
  MethodError: no method matching sunpos(::BasicAlgorithm, ::TimeZones.ZonedDateTime)
  The function `sunpos` exists, but no method is defined for this combination of argument types.
  Stacktrace:
    [1] (::SolarPosition.PositionInterface.var"#1#2")(a::Arguments{(:algorithm, :timestamp), Tuple{BasicAlgorithm, TimeZones.ZonedDateTime}})

So for some reason when I run @test Interfaces.test() it does not find the correct implementation of the sunpos function. It does find the right algorithm implementation.

With a lot of this more advanced stuff I am still a beginner so please let me know if this is completely the wrong direction to take this implementation…

I think the issue is just that you don’t attach the methods to right function, i.e. there is a namespacing issue.
See you define the interface for the function PositionInterface.sunpos (as posted above) but in Basic.jl you don’t import it and thus don’t attach a method to it. Instead what happens is, that the definition in Basic.jl creates another function Basic.sunpos which is distinct from PositionInterface.sunpos and you then export that function from Basic.jl. So of course when you test the interface, there is appropriate method of PositionInterface.sunpos for your BasicAlgorithm.

What you should do instead:

  • export the function sunpos from PositionInterface. This is somewhat optional, but I think desired since you want to use the function sunpos as part of the public interface and thus exporting it makes sense.
  • add a import .PositionInterface to Basic.jl and change the definition of sunpos to PositionInterface.sunpos to properly add the method to the correct function. This steps works independently of the previous one, i.e. does not require the export sunpos from PositionInterface
  • I would recommend not exporting sunpos from Basic.jl again. In principle it works and can also be correct if you take care to not accidently create another sunpos but it’s bad style imo. Just export it once from PositionInterface where it originates if you want it to be exported.
1 Like

Thank you very much for writing this out. I was able to fix the issue. I had some wrong ideas about how the methods are discovered, but now I know :slight_smile:

One small note:

I needed import ..PositionInterface here, then it works.