From 08661471e0e8dc1ace8663ac2ff02c1f60d84dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= Date: Wed, 14 Jun 2023 17:25:58 +0200 Subject: [PATCH 1/3] add sdf and mol generation from inchi string --- src/inchi.jl | 181 ++++++++++++++++++++++++++++++++++++++++++++------ test/inchi.jl | 7 ++ 2 files changed, 169 insertions(+), 19 deletions(-) diff --git a/src/inchi.jl b/src/inchi.jl index 7d84da55..de7ac561 100644 --- a/src/inchi.jl +++ b/src/inchi.jl @@ -4,7 +4,7 @@ # export - inchi, inchikey + inchi, inchikey, inchitomol, inchitosdf using libinchi_jll @@ -17,35 +17,127 @@ mutable struct inchi_Output inchi_Output() = new(C_NULL, C_NULL, C_NULL, C_NULL) end +mutable struct inchi_InputINCHI + # the caller is responsible for the data allocation and deallocation + szInChI::Cstring # InChI ASCIIZ string to be converted to a strucure + szOptions::Cstring # InChI options: space-delimited; each is preceded by + # '/' or '-' depending on OS and compiler +end + +mutable struct inchi_Input + # the caller is responsible for the data allocation and deallocation + atom::Ptr{Cvoid} # array of num_atoms elements + stereo0D::Ptr{Cvoid} # array of num_stereo0D 0D stereo elements or NULL + szOptions::Cstring # InChI options: space-delimited; each is preceded by + # '/' or '-' depending on OS and compiler + num_atoms::Cshort # number of atoms in the structure < MAX_ATOMS + num_stereo0D::Cshort # number of 0D stereo elements +end + +mutable struct inchi_InputEx + # the caller is responsible for the data allocation and deallocation + atom::Ptr{Cvoid} # array of num_atoms elements + stereo0D::Ptr{Cvoid} # array of num_stereo0D 0D stereo elements or NULL + szOptions::Cstring # InChI options: space-delimited; each is preceded by + # '/' or '-' depending on OS and compiler + num_atoms::Cshort # number of atoms in the structure < MAX_ATOMS + num_stereo0D::Cshort # number of 0D stereo elements + polymer::Ptr{Cvoid} + v3000::Ptr{Cvoid} +end + +mutable struct inchi_OutputStruct + # the caller is responsible for the data allocation and deallocation + atom::Ptr{Cvoid} # array of num_atoms elements + stereo0D::Ptr{Cvoid} # array of num_stereo0D 0D stereo elements or NULL + num_atoms::Cshort; # number of atoms in the structure < MAX_ATOMS + num_stereo0D::Cshort # number of 0D stereo elements + szMessage::Cstring # Error/warning ASCIIZ message + szLog::Cstring # log-file ASCIIZ string, contains a human-readable list + # of recognized options and possibly an Error/warn message + warningflags::NTuple{2, NTuple{2, Clong}} # warnings, see INCHIDIFF in inchicmp.h */ + # [x][y]: + # x=0 => Reconnected if present in InChI + # otherwise Disconnected/Normal + # x=1 => Disconnected layer if Reconnected layer is present + # y=1 => Main layer or Mobile-H + # y=0 => Fixed-H layer + + inchi_OutputStruct() = new(C_NULL, C_NULL, 0, 0, C_NULL, C_NULL, ((0, 0), (0, 0))) +end + +mutable struct inchi_OutputStructEx + # the caller is responsible for the data allocation and deallocation + atom::Ptr{Cvoid} # array of num_atoms elements + stereo0D::Ptr{Cvoid} # array of num_stereo0D 0D stereo elements or NULL + num_atoms::Cshort; # number of atoms in the structure < MAX_ATOMS + num_stereo0D::Cshort # number of 0D stereo elements + szMessage::Cstring # Error/warning ASCIIZ message + szLog::Cstring # log-file ASCIIZ string, contains a human-readable list + # of recognized options and possibly an Error/warn message + warningflags::NTuple{2, NTuple{2, Clong}} # warnings, see INCHIDIFF in inchicmp.h */ + # [x][y]: + # x=0 => Reconnected if present in InChI + # otherwise Disconnected/Normal + # x=1 => Disconnected layer if Reconnected layer is present + # y=1 => Main layer or Mobile-H + # y=0 => Fixed-H layer + polymer::Ptr{Cvoid} + v3000::Ptr{Cvoid} + + inchi_OutputStructEx() = new(C_NULL, C_NULL, 0, 0, C_NULL, C_NULL, ((0, 0), (0, 0)), C_NULL, C_NULL) +end + +function opt_array(options::String) + isempty(options) ? SubString{String}[] : lstrip.(split(options, ' ', keepempty = false), Ref(['-', '/'])) +end + +function opt_string(options::Vector{<:AbstractString}) + join((Sys.iswindows() ? '/' : '-') .* options, ' ') +end + +function unsafe_info(cs::Cstring, title::String = "") + if cs != C_NULL + @info string(title, isempty(title) ? "" : ": ", unsafe_string(cs)) + end +end + +function report_output(output, verbose::Bool) + if output.szInChI == C_NULL || verbose + output.szInChI == C_NULL && @info "InChI error with $inchi or $options" + unsafe_info(output.szMessage, "message") + unsafe_info(output.szMessage, "log") + end +end """ - inchi(molblock::String) -> Union{String,Nothing} - inchi(mol::MolGraph) -> Union{String,Nothing} + inchi(molblock::String; options::String = "", verbose::Bool = false) -> Union{String,Nothing} + inchi(mol::MolGraph; options::String = "", verbose::Bool = false) -> Union{String,Nothing} + +Generate InChI string from molblock string or molecule. -Generate InChI string from molblock string or molecule +Options, e.g. "SNon" for 'no stereo information' are specified in https://github.com/mojaie/libinchi/blob/master/INCHIBASE/src/inchiapi.h """ -function inchi(molblock::String) +function inchi(molblock::String; options::String = "", verbose::Bool = false) + # support the correct options format depending on OS + # add a timeout of 60s per molecule + opts = opt_array(options) + any(occursin.(r"^Wm?\d+$", opts)) || push!(opts, "W60") + options = opt_string(opts) + output = inchi_Output() @ccall libinchi.MakeINCHIFromMolfileText( - molblock::Cstring, "-W60"::Cstring, output::Ref{inchi_Output})::Int32 - if output.szInChI == C_NULL - @info "InChI error with $(molblock)" - if output.szMessage != C_NULL - @info "message: $(unsafe_string(output.szMessage))" - end - if output.szLog != C_NULL - @info "log: $(unsafe_string(output.szLog))" - end - res = nothing # TODO: can be type stable? - else - res = unsafe_string(output.szInChI) - end + molblock::Cstring, options::Cstring, output::Ref{inchi_Output})::Int32 + report_output(output, verbose) + + res = output.szInChI == C_NULL ? nothing : unsafe_string(output.szInChI) + # Free string buffers allocated by MakeINCHIFromMolfileText @ccall libinchi.FreeINCHI(output::Ref{inchi_Output})::Cvoid return res end -inchi(mol::MolGraph) = inchi(printv2mol(mol)) +inchi(mol::MolGraph; options::String = "", verbose = false) = inchi(printv2mol(mol); options, verbose) """ @@ -67,3 +159,54 @@ function inchikey(inchi::Union{String,Nothing}) end inchikey(mol::MolGraph) = inchikey(inchi(mol)) + +""" + inchitosdf(inchi::String; options::String = "") -> Union{String,Nothing} + +Generate sdf string from inchi string, `options` are specified in https://github.com/mojaie/libinchi/blob/master/INCHI_BASE/src/inchi_api.h +""" +function inchitosdf(inchi::String; options::String = "", verbose::Bool = false) + # support the correct options format depending on OS + opts = opt_array(options) + # add a timeout of 60s per molecule + any(occursin.(r"^Wm?\d+$", opts)) || push!(opts, "W60") + # switch output to sdf format + "OutputSDF" ∈ opts || push!(opts, "OutputSDF") + options = opt_string(opts) + + structure = inchi_OutputStructEx() + inchi_input = inchi_InputINCHI(Base.unsafe_convert(Cstring, inchi), Base.unsafe_convert(Cstring, options)) + @ccall libinchi.GetStructFromINCHIEx( + inchi_input::Ref{inchi_InputINCHI}, structure::Ref{inchi_OutputStructEx})::Int32 + + input = inchi_InputEx( + structure.atom, + structure.stereo0D, + Base.unsafe_convert(Cstring, options), + structure.num_atoms, + structure.num_stereo0D, + structure.polymer, + structure.v3000 + ) + output = inchi_Output() + @ccall libinchi.GetINCHIEx( + input::Ref{inchi_InputEx}, output::Ref{inchi_Output})::Cint + report_output(output, verbose) + + res = output.szInChI == C_NULL ? nothing : unsafe_string(output.szInChI) + + # Free buffers allocated by GetStructFromINCHIEx and GetINCHI + @ccall libinchi.FreeStructFromINCHIEx(structure::Ref{inchi_OutputStructEx})::Cvoid + @ccall libinchi.FreeINCHI(output::Ref{inchi_Output})::Cvoid + + return res +end + +""" + function inchitomol(inchi::String; options = "", verbose = false) + +Generate molecule from inchi string, `options` are specified in https://github.com/mojaie/libinchi/blob/master/INCHI_BASE/src/inchi_api.h +""" +function inchitomol(inchi::String; options = "", verbose = false) + inchitosdf(inchi; options, verbose) |> IOBuffer |> sdftomol +end \ No newline at end of file diff --git a/test/inchi.jl b/test/inchi.jl index 4425dc89..07fb3b3d 100644 --- a/test/inchi.jl +++ b/test/inchi.jl @@ -10,4 +10,11 @@ ikey2 = inchikey(inchi2) @test inchi1 == inchi2 @test ikey1 == ikey2 + mol_from_inchi = inchitomol(inchi1) + inchi1_no_stereo = inchi(txt, options = "SNon") + inchi3 = inchi(mol_from_inchi) + # lbinchi version 1.05 doesn't support chiral sdf output, therefore test without stereo information + # as soon as version 1.06 is in place, the lines below need to be adapted + @test_broken inchi3 == inchi1 + @test inchi3 == inchi1_no_stereo end From a58401d96da9d64a23717731d8e2e81f9229ad51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= Date: Wed, 14 Jun 2023 17:26:24 +0200 Subject: [PATCH 2/3] fix VERSION parsing on windows --- src/util/meta.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/meta.jl b/src/util/meta.jl index a3f72f0d..8fadc518 100644 --- a/src/util/meta.jl +++ b/src/util/meta.jl @@ -6,5 +6,5 @@ const VERSION = begin io = open(joinpath(dirname(@__FILE__), "..", "..", "Project.toml")) readuntil(io, "version = \"") - VersionNumber(readuntil(io, "\"\n")) + VersionNumber(readuntil(io, "\"")) end From 48564cbc521fb77a5131ae24faf6729c9d4cc0fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= Date: Mon, 19 Jun 2023 17:08:25 +0200 Subject: [PATCH 3/3] add coordgen! in inchitomol --- src/inchi.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/inchi.jl b/src/inchi.jl index de7ac561..88781a6d 100644 --- a/src/inchi.jl +++ b/src/inchi.jl @@ -207,6 +207,8 @@ end Generate molecule from inchi string, `options` are specified in https://github.com/mojaie/libinchi/blob/master/INCHI_BASE/src/inchi_api.h """ -function inchitomol(inchi::String; options = "", verbose = false) - inchitosdf(inchi; options, verbose) |> IOBuffer |> sdftomol +function inchitomol(inchi::String; options = "", verbose = false, coordgen = true) + mol = inchitosdf(inchi; options, verbose) |> IOBuffer |> sdftomol + coordgen && coordgen!(mol) + return mol end \ No newline at end of file