mirror of
https://github.com/Cian-H/nanoconc.git
synced 2025-12-22 14:12:00 +00:00
Implemented property based testing via PyCall.jl and hypothesis.py
This commit is contained in:
161
.gitignore
vendored
161
.gitignore
vendored
@@ -72,6 +72,167 @@ Manifest.toml
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Project specific gitignores
|
||||
# Original notes from when developing
|
||||
notes/*
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
name = "nanoconc"
|
||||
uuid = "9a947172-b1ea-4b16-84a6-f3d50752424d"
|
||||
authors = ["Cian Hughes <chughes000@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
[deps]
|
||||
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||
Debugger = "31a5f54b-26ea-5ae9-a837-f05ce5417438"
|
||||
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
|
||||
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
|
||||
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
|
||||
Memoize = "c03570c3-d221-55d1-a50c-7939bbd78826"
|
||||
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
|
||||
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
|
||||
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
|
||||
XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0"
|
||||
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
attrs==23.2.0
|
||||
hypothesis==6.98.4
|
||||
numpy==1.26.4
|
||||
sortedcontainers==2.4.0
|
||||
5
setup_venv.sh
Executable file
5
setup_venv.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
@@ -49,6 +49,7 @@ columns entitled "w","n" and "k" for wavelength in nm, n and k respectively)
|
||||
function addmaterial(z::Float64, am::Float64, rho::Float64, res::Float64,
|
||||
filepath::String, material::String, description::String;
|
||||
disp::Bool=true)
|
||||
flag = false
|
||||
try
|
||||
flag = h5open(matfile, "r") do file
|
||||
has(file, material)
|
||||
@@ -96,6 +97,7 @@ An alternative version of the addmaterial function for materials with known mie
|
||||
function addmaterial(omp::Float64, om0::Float64, fv::Float64,
|
||||
filepath::String, material::String, description::String;
|
||||
disp::Bool=true)
|
||||
flag = false
|
||||
try
|
||||
flag = h5open(matfile, "r") do file
|
||||
has(file, material)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[deps]
|
||||
AirspeedVelocity = "1c8270ee-6884-45cc-9545-60fa71ec23e4"
|
||||
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
|
||||
PropCheck = "ca382230-33be-11e9-0059-d981d03070e4"
|
||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||
|
||||
@@ -67,10 +67,10 @@ if abspath(PROGRAM_FILE) == @__FILE__
|
||||
current_package_version = Pkg.TOML.parsefile("$ROOT_DIR/Project.toml")["version"]
|
||||
|
||||
function display_to_file(io, x)
|
||||
show(IOContext(io, :limit => false), "text/plain", x)
|
||||
show(IOContext(io, :limit => false, :color => true), "text/plain", x)
|
||||
end
|
||||
|
||||
open("$ROOT_DIR/benchmarks/$current_package_version.txt", "w") do io
|
||||
open("$ROOT_DIR/benchmarks/$current_package_version.ansi", "w") do io
|
||||
println(io, "C Implementation")
|
||||
display_to_file(io, result[1])
|
||||
println(io, "\n\nFortran Implementation")
|
||||
|
||||
@@ -53,6 +53,6 @@ done
|
||||
|
||||
# And, finally, we can compile the C, and Fortran implementations
|
||||
cd $bhmie_dir
|
||||
gcc -shared -fPIC -o bhmie-c/bhmie.so bhmie-c/bhmie.c bhmie-c/complex.c bhmie-c/nrutil.c -lm -Wno-builtin-declaration-mismatch -Wno-implicit-function-declaration
|
||||
gfortran -shared -fPIC -o bhmie-f/bhmie.so bhmie-f/bhmie.f
|
||||
gfortran -shared -fPIC -o bhmie-f/bhmie_f77.so bhmie-f/bhmie_f77.f
|
||||
gcc -g -shared -fPIC -o bhmie-c/bhmie.so bhmie-c/bhmie.c bhmie-c/complex.c bhmie-c/nrutil.c -lm -Wno-builtin-declaration-mismatch -Wno-implicit-function-declaration
|
||||
gfortran -g -shared -fPIC -o bhmie-f/bhmie.so bhmie-f/bhmie.f
|
||||
gfortran -g -shared -fPIC -o bhmie-f/bhmie_f77.so bhmie-f/bhmie_f77.f
|
||||
@@ -1,9 +1,13 @@
|
||||
using Test
|
||||
using Random
|
||||
using PropCheck
|
||||
using Debugger
|
||||
using PyCall
|
||||
|
||||
|
||||
include("../anchors.jl")
|
||||
|
||||
import .Anchors: TEST_DIR, SRC_DIR
|
||||
import .Anchors: TEST_DIR, SRC_DIR, ROOT_DIR
|
||||
|
||||
if !@isdefined TestUtils
|
||||
include(joinpath(TEST_DIR, "testutils.jl"))
|
||||
@@ -15,85 +19,129 @@ if !@isdefined FFIWraps
|
||||
include(joinpath(TEST_DIR, "ffi_wraps.jl"))
|
||||
end
|
||||
|
||||
# function julia_vs_c(args::Tuple{Float64, Float64, Float64, UInt32, Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}})
|
||||
# x, cxref_re, cxref_im, nang, cxs1_re, cxs1_im, cxs2_re, cxs2_im = args
|
||||
function julia_vs_c(x, cxref_re, cxref_im, nang, cxs1_re, cxs1_im, cxs2_re, cxs2_im)
|
||||
cxref, cxs1, cxs2 = ComplexF64(cxref_re, cxref_im), ComplexF64.(cxs1_re, cxs1_im), ComplexF64.(cxs2_re, cxs2_im)
|
||||
x_c, cxref_c, nang_c, cxs1_c, cxs2_c = Float32(x), ComplexF32(cxref), UInt32(nang), ComplexF32.(cxs1), ComplexF32.(cxs2)
|
||||
return isapprox(
|
||||
miemfp.bhmie(x, cxref, nang),
|
||||
FFIWraps.bhmie_c(x_c, cxref_c, nang_c, cxs1_c, cxs2_c),
|
||||
rtol=0.1,
|
||||
)
|
||||
|
||||
# Set up the Python environment
|
||||
run(`$ROOT_DIR/setup_venv.sh`)
|
||||
ENV["PYTHON"] = joinpath(ROOT_DIR, ".venv/bin/python")
|
||||
|
||||
@pyinclude(joinpath(TEST_DIR, "miemfp_tests.py"))
|
||||
|
||||
|
||||
miemfp.bhmie(
|
||||
x::Float64,
|
||||
cxref::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
) = miemfp.bhmie(x, cxref, UInt32(nang))
|
||||
|
||||
function miemfp.bhmie(
|
||||
x::Float64,
|
||||
cxref::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
event::PyObject,
|
||||
)
|
||||
event.clear()
|
||||
result = miemfp.bhmie(x, cxref, nang, s1, s2)
|
||||
event.set()
|
||||
return result
|
||||
end
|
||||
|
||||
# function julia_vs_fortran(args::Tuple{Float64, Float64, Float64, UInt32, Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}})
|
||||
# x, cxref_re, cxref_im, nang, cxs1_re, cxs1_im, cxs2_re, cxs2_im = args
|
||||
function julia_vs_fortran(x, cxref_re, cxref_im, nang, cxs1_re, cxs1_im, cxs2_re, cxs2_im)
|
||||
cxref, cxs1, cxs2 = ComplexF64(cxref_re, cxref_im), ComplexF64.(cxs1_re, cxs1_im), ComplexF64.(cxs2_re, cxs2_im)
|
||||
x_f, cxref_f, nang_f, cxs1_f, cxs2_f = Float32(x), ComplexF32(cxref), Int32(nang), ComplexF32.(cxs1), ComplexF32.(cxs2)
|
||||
b = miemfp.bhmie(x, cxref, nang)
|
||||
f = FFIWraps.bhmie_fortran(x_f, cxref_f, nang_f, cxs1_f, cxs2_f)
|
||||
# open("bhmie_julia_vs_fortran.txt", "a") do io
|
||||
# println(io, "julia: ", b)
|
||||
# println(io, "fortran: ", f)
|
||||
# end
|
||||
# return is_approx(b, f)
|
||||
return isapprox(
|
||||
miemfp.bhmie(x, cxref, nang),
|
||||
FFIWraps.bhmie_fortran(x_f, cxref_f, nang_f, cxs1_f, cxs2_f),
|
||||
rtol=0.1,
|
||||
)
|
||||
end
|
||||
|
||||
# function julia_vs_fortran77(args::Tuple{Float64, Float64, Float64, UInt32, Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}})
|
||||
# x, cxref_re, cxref_im, nang, cxs1_re, cxs1_im, cxs2_re, cxs2_im = args
|
||||
function julia_vs_fortran77(x, cxref_re, cxref_im, nang, cxs1_re, cxs1_im, cxs2_re, cxs2_im)
|
||||
cxref, cxs1, cxs2 = ComplexF64(cxref_re, cxref_im), ComplexF64.(cxs1_re, cxs1_im), ComplexF64.(cxs2_re, cxs2_im)
|
||||
x_f, cxref_f, nang_f, cxs1_f, cxs2_f = Float32(x), ComplexF32(cxref), Int32(nang), ComplexF32.(cxs1), ComplexF32.(cxs2)
|
||||
return isapprox(
|
||||
miemfp.bhmie(x, cxref, nang),
|
||||
FFIWraps.bhmie_fortran77(x_f, cxref_f, nang_f, cxs1_f, cxs2_f),
|
||||
rtol=0.1,
|
||||
)
|
||||
end
|
||||
|
||||
f64_gen = PropCheck.itype(Float64)
|
||||
UInt32_gen = PropCheck.itype(UInt32)
|
||||
f64_vec_gen = PropCheck.vector(isample(1:100), f64_gen)
|
||||
bhmie_gen = PropCheck.interleave(
|
||||
f64_gen,
|
||||
f64_gen,
|
||||
f64_gen,
|
||||
UInt32_gen,
|
||||
f64_vec_gen,
|
||||
f64_vec_gen,
|
||||
f64_vec_gen,
|
||||
f64_vec_gen,
|
||||
FFIWraps.bhmie_fortran(
|
||||
x::Float64,
|
||||
refrel::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
) = FFIWraps.bhmie_fortran(
|
||||
Float32(x),
|
||||
ComplexF32(refrel),
|
||||
Int32(nang),
|
||||
ComplexF32.(s1),
|
||||
ComplexF32.(s2),
|
||||
)
|
||||
|
||||
@testset "bhmie" begin
|
||||
function FFIWraps.bhmie_fortran(
|
||||
x::Float64,
|
||||
refrel::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
event::PyObject,
|
||||
)
|
||||
event.clear()
|
||||
result = FFIWraps.bhmie_fortran(x, refrel, nang, s1, s2)
|
||||
event.set()
|
||||
return result
|
||||
end
|
||||
|
||||
FFIWraps.bhmie_fortran77(
|
||||
x::Float64,
|
||||
refrel::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
) = FFIWraps.bhmie_fortran77(
|
||||
Float32(x),
|
||||
ComplexF32(refrel),
|
||||
Int32(nang),
|
||||
ComplexF32.(s1),
|
||||
ComplexF32.(s2),
|
||||
)
|
||||
|
||||
function FFIWraps.bhmie_fortran77(
|
||||
x::Float64,
|
||||
refrel::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
event::PyObject,
|
||||
)
|
||||
event.clear()
|
||||
result = FFIWraps.bhmie_fortran77(x, refrel, nang, s1, s2)
|
||||
event.set()
|
||||
return result
|
||||
end
|
||||
|
||||
FFIWraps.bhmie_c(
|
||||
x::Float64,
|
||||
refrel::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
) = FFIWraps.bhmie_c(
|
||||
Float32(x),
|
||||
ComplexF32(refrel),
|
||||
UInt32(nang),
|
||||
ComplexF32.(s1),
|
||||
ComplexF32.(s2),
|
||||
)
|
||||
|
||||
function FFIWraps.bhmie_c(
|
||||
x::Float64,
|
||||
refrel::ComplexF64,
|
||||
nang::Int64,
|
||||
s1::Vector{ComplexF64},
|
||||
s2::Vector{ComplexF64},
|
||||
event::PyObject,
|
||||
)
|
||||
event.clear()
|
||||
result = FFIWraps.bhmie_c(x, refrel, nang, s1, s2)
|
||||
event.set()
|
||||
return result
|
||||
end
|
||||
|
||||
@testset "miemfp" begin
|
||||
@testset "miemfp.bhmie" begin
|
||||
c_check = PropCheck.check(julia_vs_c, bhmie_gen)
|
||||
c_result = c_check == true
|
||||
if !c_result
|
||||
println("Fail vs C, PropCheck:")
|
||||
display(c_check)
|
||||
end
|
||||
@test c_result
|
||||
f_check = PropCheck.check(julia_vs_fortran, bhmie_gen)
|
||||
f_result = f_check == true
|
||||
if !f_result
|
||||
println("Fail vs Fortran, PropCheck:")
|
||||
display(f_check)
|
||||
end
|
||||
@test f_result
|
||||
f77_check = PropCheck.check(julia_vs_fortran77, bhmie_gen)
|
||||
f77_result = f77_check == true
|
||||
if !f77_result
|
||||
println("Fail vs Fortran77, PropCheck:")
|
||||
display(f77_check)
|
||||
end
|
||||
@test f77_result
|
||||
event1, event2 = py"asyncio.Event"(), py"asyncio.Event"()
|
||||
event1.set(), event2.set()
|
||||
result, output = py"compare_bhmie_functions"(miemfp.bhmie, FFIWraps.bhmie_fortran, event1, event2)
|
||||
@test result
|
||||
result, output = py"compare_bhmie_functions"(miemfp.bhmie, FFIWraps.bhmie_fortran77, event1, event2)
|
||||
@test result
|
||||
result, output = py"compare_bhmie_functions"(miemfp.bhmie, FFIWraps.bhmie_c, event1, event2)
|
||||
@test result
|
||||
end
|
||||
end
|
||||
49
test/miemfp_tests.py
Normal file
49
test/miemfp_tests.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from typing import List, Tuple
|
||||
import asyncio
|
||||
import numpy as np
|
||||
from hypothesis import errors, given, settings, strategies as st # type: ignore
|
||||
|
||||
def compare_bhmie_functions(f1, f2, event1: asyncio.Event, event2: asyncio.Event) -> Tuple[bool, str]:
|
||||
async def async_closure(
|
||||
x: float,
|
||||
cxref: Tuple[float, float],
|
||||
cxs1: List[Tuple[float, float]],
|
||||
cxs2: List[Tuple[float, float]],
|
||||
) -> bool:
|
||||
cxref = complex(*cxref)
|
||||
cxs1 = [complex(*c) for c in cxs1]
|
||||
cxs2 = [complex(*c) for c in cxs2]
|
||||
|
||||
# This is to ensure that only one instance of each function is running at a time
|
||||
# to avoid memory issues in the FFI code
|
||||
await event1.wait()
|
||||
f1_result = f1(x, cxref, 2, cxs1, cxs2)[:2]
|
||||
await event2.wait()
|
||||
f2_result = f2(x, cxref, 2, cxs1, cxs2)[:2]
|
||||
|
||||
return np.all(np.isclose(f1_result, f2_result))
|
||||
|
||||
@settings(deadline=None)
|
||||
@given(
|
||||
# Must be bigger than an atom but still nanoscale
|
||||
x=st.floats(min_value=0.1, max_value=100),
|
||||
# Refractive indeces must be within a physically reasonable range
|
||||
cxref=st.tuples(st.floats(min_value=0.1, max_value=4.0), st.floats(min_value=0.1, max_value=4.0)),
|
||||
cxs1=st.lists(st.tuples(st.floats(min_value=0.1, allow_infinity=False), st.floats(min_value=0.1, allow_infinity=False)), min_size=10, max_size=100),
|
||||
cxs2=st.lists(st.tuples(st.floats(min_value=0.1, allow_infinity=False), st.floats(min_value=0.1, allow_infinity=False)), min_size=10, max_size=100),
|
||||
)
|
||||
def sync_closure(
|
||||
x: float,
|
||||
cxref: Tuple[float, float],
|
||||
cxs1: List[Tuple[float, float]],
|
||||
cxs2: List[Tuple[float, float]],
|
||||
) -> bool:
|
||||
assert asyncio.run(async_closure(x, cxref, cxs1, cxs2))
|
||||
|
||||
try:
|
||||
sync_closure()
|
||||
return True, "Test passed"
|
||||
except AssertionError as e:
|
||||
return False, f"AssertionError: {str(e)}"
|
||||
except errors.HypothesisException as e:
|
||||
return False, f"HypothesisException: {str(e)}"
|
||||
@@ -22,4 +22,9 @@ function test_from_serialized(fn::Function, filename::String)
|
||||
@test deep_compare([fn(a...; kw...) for (a, kw) in argskwargs], out)
|
||||
end
|
||||
|
||||
end # module TestUtils
|
||||
function asymmetric_floatvec_to_complexvec(vec_a::Vector{Float32}, vec_b::Vector{Float32})
|
||||
shortest = min(length(vec_a), length(vec_b))
|
||||
ComplexF32.(vec_a[1:shortest], vec_b[1:shortest])
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user