diff --git a/src/interfaces/setup_interface.jl b/src/interfaces/setup_interface.jl index 5798a6a..da5e8e0 100644 --- a/src/interfaces/setup_interface.jl +++ b/src/interfaces/setup_interface.jl @@ -27,11 +27,11 @@ quantity depends on is kept constant. Every subtype of `AbstractComputationSetup` should implement the interface function ```Julia - _is_valid_input(stp::AbstractComputationSetup, input) # default: true + _assert_valid_input(stp::AbstractComputationSetup, input) ``` - which should return true iff the `input` is valid for the computation of the associated quantity (see [`_is_valid_input`](@ref) and [`_assert_valid_input`](@ref) for more details). - The default implementation always returns `true`. Provide a custom implementation if a different behavior is required. + which should throw and an exception subtyped from [`AbstractInvalidInputException`](@ref) if the `input` is not valid for the computation of the associated quantity (see [`_is_valid_input`](@ref) and [`_assert_valid_input`](@ref) for more details). + The default implementation does nothing, i.e. every input is valid by default. Provide a custom implementation if a different behavior is required. ## Actual computation @@ -61,31 +61,24 @@ abstract type AbstractComputationSetup end _is_computation_setup(::AbstractComputationSetup) = true """ +Abstract base type for exceptions indicating invalid input. See [`InvalidInputError`](@ref) for a simple concrete implementation. +Concrete implementations should at least implement - _is_valid_input(stp::AbstractComputationSetup, input::Any) - -Interface function, which returns true if the constraints of the `input` associated with the quantity of `stp` are met. -This function is called to validate the input of [`compute`](@ref) before calling [`_compute`](@ref). - -!!! note "Default implementation" - - Since no input validation is equivalent to every input being valid, this function returns `true` by default. - This behavior can be overwritten if actual validation is necessary. - +```Julia -An assert version of this function is given by [`_assert_valid_input`](@ref), which directly uses the output of this function. +Base.showerror(io::IO, err::CustomInvalidError) where {CustomInvalidError<:AbstractInvalidInputException} +``` """ -@inline function _is_valid_input(stp::AbstractComputationSetup, input) - return true -end +abstract type AbstractInvalidInputException <: Exception end + """ InvalidInputError(msg::String) Exception which is thrown if a given input is invalid, e.g. passed to [`_assert_valid_input`](@ref). """ -struct InvalidInputError <: Exception +struct InvalidInputError <: AbstractInvalidInputException msg::String end Base.showerror(io::IO, err::InvalidInputError) = println(io, "InvalidInputError: $(err.msg).") @@ -111,11 +104,38 @@ Interface function, which asserts that the given `input` is valid, and throws an """ @inline function _assert_valid_input(stp::AbstractComputationSetup,input) - _is_valid_input(stp,input) || throw(InvalidInputError("Something wrong with the input!\n setup:$stp \n input:$input")) - return nothing end +""" + + _is_valid_input(stp::AbstractComputationSetup, input::Any) + +Interface function, which returns true if the constraints of the `input` associated with the quantity of `stp` are met. +This function is called to validate the input of [`compute`](@ref) before calling [`_compute`](@ref). + +!!! note "Default implementation" + + Since no input validation is equivalent to every input being valid, this function returns `true` by default. + This behavior can be overwritten if actual validation is necessary. + + +An assert version of this function is given by [`_assert_valid_input`](@ref), which directly uses the output of this function. + +""" +@inline function _is_valid_input(stp::AbstractComputationSetup, input) + try + _assert_valid_input(stp,input) + catch e + if isa(e, AbstractInvalidInputException) + return false + end + @warn "The function _assert_valid_input throws an Exception, which is not an InvalidInputError! The Exception thrown is: " + throw(e) + end + return true +end + """ function _post_processing(stp::AbstractComputationSetup, input::Any, result::Any) diff --git a/test/interfaces/setup_interface.jl b/test/interfaces/setup_interface.jl index a20a26a..c732b8f 100644 --- a/test/interfaces/setup_interface.jl +++ b/test/interfaces/setup_interface.jl @@ -9,6 +9,11 @@ RTOL = sqrt(eps()) _groundtruth_compute(x) = x _groundtruth_input_validation(x) = (x>0) +struct TestException <: QEDprocesses.AbstractInvalidInputException end +function _groundtruth_valid_input_assert(x) + _groundtruth_input_validation(x)||throw(TestException()) + nothing +end _transform_to_invalid(x) = -abs(x) _groundtruth_post_processing(x,y) = x+y @@ -19,18 +24,9 @@ QEDprocesses._compute(stp::AbstractTestSetup, x) = _groundtruth_compute(x) # setup with default implementations struct TestSetupDefault <: AbstractTestSetup end -# setup with custom _is_valid_input -struct TestSetupCustomIsValidInput <: AbstractTestSetup end -QEDprocesses._is_valid_input(::TestSetupCustomIsValidInput, x) = _groundtruth_input_validation(x) - # setup with custom _assert_valid_input struct TestSetupCustomAssertValidInput<: AbstractTestSetup end -QEDprocesses._is_valid_input(::TestSetupCustomAssertValidInput, x) = _groundtruth_input_validation(x) -struct TestException <: Exception end -function QEDprocesses._assert_valid_input(stp::TestSetupCustomAssertValidInput, x) - QEDprocesses._is_valid_input(stp,x)||throw(TestException()) - return nothing -end +QEDprocesses._assert_valid_input(stp::TestSetupCustomAssertValidInput, x) = _groundtruth_valid_input_assert(x) # setup with custom post processing struct TestSetupCustomPostProcessing<: AbstractTestSetup end @@ -38,15 +34,16 @@ QEDprocesses._post_processing(::TestSetupCustomPostProcessing,x,y) = _groundtrut # setup with custom input validation and post processing struct TestSetupCustom <: AbstractTestSetup end -QEDprocesses._is_valid_input(::TestSetupCustom, x) = _groundtruth_input_validation(x) +QEDprocesses._assert_valid_input(stp::TestSetupCustom, x) = _groundtruth_valid_input_assert(x) QEDprocesses._post_processing(::TestSetupCustom,x,y) = _groundtruth_post_processing(x,y) # setup which fail on computation with default implementations struct TestSetupFAIL <: AbstractComputationSetup end -# setup which fail on computation with custom input validation +# setup which fail on computation with custom input validation, where the +# invalid input will be caught before the computation. struct TestSetupCustomValidationFAIL <: AbstractComputationSetup end -QEDprocesses._is_valid_input(::TestSetupCustomValidationFAIL, x) = _groundtruth_input_validation(x) +QEDprocesses._assert_valid_input(stp::TestSetupCustomValidationFAIL, x) = _groundtruth_valid_input_assert(x) # setup which fail on computation with custom post processing struct TestSetupCustomPostProcessingFAIL <: AbstractComputationSetup end @@ -54,13 +51,15 @@ QEDprocesses._post_processing(::TestSetupCustomPostProcessingFAIL,x,y) = _ground @testset "general computation setup interface" begin @testset "interface fail" begin rnd_input = rand(RNG) + @test_throws MethodError QEDprocesses._compute(TestSetupFAIL(), rnd_input) @test_throws MethodError compute(TestSetupFAIL(), rnd_input) @test_throws MethodError QEDprocesses._compute(TestSetupCustomValidationFAIL(), rnd_input) @test_throws MethodError compute(TestSetupCustomValidationFAIL(), rnd_input) # invalid input should be caught without throwing a MethodError - @test_throws InvalidInputError compute(TestSetupCustomValidationFAIL(), _transform_to_invalid(rnd_input)) + @test_throws TestException compute(TestSetupCustomValidationFAIL(), _transform_to_invalid(rnd_input)) + @test_throws MethodError QEDprocesses._compute(TestSetupCustomPostProcessingFAIL(), rnd_input) @test_throws MethodError compute(TestSetupCustomPostProcessingFAIL(), rnd_input) @@ -78,19 +77,13 @@ QEDprocesses._post_processing(::TestSetupCustomPostProcessingFAIL,x,y) = _ground end @testset "custom input validation" begin - stp = TestSetupCustomIsValidInput() + stp = TestSetupCustomAssertValidInput() rnd_input = rand(RNG) @test QEDprocesses._is_valid_input(stp, _groundtruth_input_validation(rnd_input)) @test !QEDprocesses._is_valid_input(stp, !_groundtruth_input_validation(rnd_input)) - @test isapprox(compute(stp, rnd_input), _groundtruth_compute(rnd_input), atol=ATOL,rtol=RTOL) - @test_throws InvalidInputError QEDprocesses._assert_valid_input(stp, _transform_to_invalid(rnd_input)) - @test_throws InvalidInputError compute(stp, _transform_to_invalid(rnd_input)) - - stp2 = TestSetupCustomAssertValidInput() - rnd_input2 = rand(RNG) - @test QEDprocesses._assert_valid_input(stp2,rnd_input2)==nothing - @test_throws TestException QEDprocesses._assert_valid_input(stp2,_transform_to_invalid(rnd_input2)) - @test_throws TestException compute(stp2, _transform_to_invalid(rnd_input2)) + @test QEDprocesses._assert_valid_input(stp,rnd_input)==nothing + @test_throws TestException QEDprocesses._assert_valid_input(stp,_transform_to_invalid(rnd_input)) + @test_throws TestException compute(stp, _transform_to_invalid(rnd_input)) end @@ -106,10 +99,10 @@ QEDprocesses._post_processing(::TestSetupCustomPostProcessingFAIL,x,y) = _ground stp = TestSetupCustom() rnd_input = rand(RNG) rnd_output = rand(RNG) - + @test QEDprocesses._is_valid_input(stp, _groundtruth_input_validation(rnd_input)) @test !QEDprocesses._is_valid_input(stp, !_groundtruth_input_validation(rnd_input)) - @test_throws InvalidInputError compute(stp, _transform_to_invalid(rnd_input)) + @test_throws TestException() compute(stp, _transform_to_invalid(rnd_input)) @test isapprox(QEDprocesses._post_processing(stp,rnd_input,rnd_output), _groundtruth_post_processing(rnd_input,rnd_output)) @test isapprox(compute(stp,rnd_input), _groundtruth_post_processing(rnd_input,_groundtruth_compute(rnd_input))) end