rdkit-rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9182da229ab652e734801366a87d7a75527888aab1d5fc4df16730cd9468dd88
4
+ data.tar.gz: 4835a455fb34e0270ff568eda78bdb560932cdb96a47b3f7e0c5cf717127f695
5
+ SHA512:
6
+ metadata.gz: 4602becf8e88cdc946b7c92926dbaec2c6dad8579f42016697b89667c8242138675ef407140556b6b1097531e1b13632c1fb408b4a21783d73770ea8752f8a3e
7
+ data.tar.gz: 7486a4a515a40a99fe7c466deb04be027070d0cc41c16979de00300662ad607f2396df1619eb13cf7c7d493832a9b8d17d90b7fe2febbd67468ebf9c6913c0e6
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2024-08-08)
2
+
3
+ - First release
data/LICENSE.txt ADDED
@@ -0,0 +1,30 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2006-2015, Rational Discovery LLC, Greg Landrum, and Julie Penzotti and others
4
+ Copyright (c) 2024 Andrew Kane
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # RDKit.rb
2
+
3
+ Cheminformatics for Ruby, powered by [RDKit](https://github.com/rdkit/rdkit)
4
+
5
+ [![Build Status](https://github.com/ankane/rdkit-ruby/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/rdkit-ruby/actions)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application’s Gemfile:
10
+
11
+ ```ruby
12
+ gem "rdkit-rb"
13
+ ```
14
+
15
+ ## Getting Started
16
+
17
+ Create a molecule
18
+
19
+ ```ruby
20
+ mol = RDKit::Molecule.from_smiles("c1ccccc1O")
21
+ ```
22
+
23
+ Get the number of atoms
24
+
25
+ ```ruby
26
+ mol.num_atoms
27
+ ```
28
+
29
+ Get substructure matches
30
+
31
+ ```ruby
32
+ mol.match(RDKit::Molecule.from_smarts("ccO"))
33
+ ```
34
+
35
+ Get fragments
36
+
37
+ ```ruby
38
+ mol.fragments
39
+ ```
40
+
41
+ Generate an SVG
42
+
43
+ ```ruby
44
+ mol.to_svg
45
+ ```
46
+
47
+ ## Fingerprints
48
+
49
+ A number of [fingerprints](https://www.rdkit.org/docs/RDKit_Book.html#additional-information-about-the-fingerprints) are supported.
50
+
51
+ RDKit
52
+
53
+ ```ruby
54
+ mol.rdkit_fingerprint
55
+ ```
56
+
57
+ Morgan
58
+
59
+ ```ruby
60
+ mol.morgan_fingerprint
61
+ ```
62
+
63
+ Pattern
64
+
65
+ ```ruby
66
+ mol.pattern_fingerprint
67
+ ```
68
+
69
+ Atom pair
70
+
71
+ ```ruby
72
+ mol.atom_pair_fingerprint
73
+ ```
74
+
75
+ Topological torsion
76
+
77
+ ```ruby
78
+ mol.topological_torsion_fingerprint
79
+ ```
80
+
81
+ MACCS
82
+
83
+ ```ruby
84
+ mol.maccs_fingerprint
85
+ ```
86
+
87
+ You can use a library like [pgvector-ruby](https://github.com/pgvector/pgvector-ruby) to find similar molecules. See an [example](https://github.com/pgvector/pgvector-ruby/blob/master/examples/morgan_fingerprints.rb).
88
+
89
+ ## Updates
90
+
91
+ Add or remove hydrogen atoms
92
+
93
+ ```ruby
94
+ mol.add_hs!
95
+ mol.remove_hs!
96
+ ```
97
+
98
+ Standardize
99
+
100
+ ```ruby
101
+ mol.cleanup!
102
+ mol.normalize!
103
+ mol.neutralize!
104
+ mol.reionize!
105
+ mol.canonical_tautomer!
106
+ mol.charge_parent!
107
+ mol.fragment_parent!
108
+ ```
109
+
110
+ ## Conversion
111
+
112
+ SMILES
113
+
114
+ ```ruby
115
+ mol.to_smiles
116
+ ```
117
+
118
+ SMARTS
119
+
120
+ ```ruby
121
+ mol.to_smarts
122
+ ```
123
+
124
+ CXSMILES
125
+
126
+ ```ruby
127
+ mol.to_cxsmiles
128
+ ```
129
+
130
+ CXSMARTS
131
+
132
+ ```ruby
133
+ mol.to_cxsmarts
134
+ ```
135
+
136
+ JSON
137
+
138
+ ```ruby
139
+ mol.to_json
140
+ ```
141
+
142
+ ## Reactions
143
+
144
+ Create a reaction
145
+
146
+ ```ruby
147
+ rxn = RDKit::Reaction.from_smarts("[CH3:1][OH:2]>>[CH2:1]=[OH0:2]")
148
+ ```
149
+
150
+ Generate an SVG
151
+
152
+ ```ruby
153
+ rxn.to_svg
154
+ ```
155
+
156
+ ## History
157
+
158
+ View the [changelog](https://github.com/ankane/rdkit-ruby/blob/master/CHANGELOG.md)
159
+
160
+ ## Contributing
161
+
162
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
163
+
164
+ - [Report bugs](https://github.com/ankane/rdkit-ruby/issues)
165
+ - Fix bugs and [submit pull requests](https://github.com/ankane/rdkit-ruby/pulls)
166
+ - Write, clarify, or fix documentation
167
+ - Suggest or add new features
168
+
169
+ To get started with development:
170
+
171
+ ```sh
172
+ git clone https://github.com/ankane/rdkit-ruby.git
173
+ cd rdkit-ruby
174
+ bundle install
175
+ bundle exec rake vendor:all
176
+ bundle exec rake test
177
+ ```
data/lib/rdkit/ffi.rb ADDED
@@ -0,0 +1,91 @@
1
+ module RDKit
2
+ module FFI
3
+ extend Fiddle::Importer
4
+
5
+ libs = Array(RDKit.ffi_lib).dup
6
+ begin
7
+ dlload Fiddle.dlopen(libs.shift)
8
+ rescue Fiddle::DLError => e
9
+ retry if libs.any?
10
+ raise e
11
+ end
12
+
13
+ # https://github.com/rdkit/rdkit/blob/master/Code/MinimalLib/cffiwrapper.h
14
+
15
+ # I/O
16
+ extern "char *get_mol(const char *input, size_t *mol_sz, const char *details_json)"
17
+ extern "char *get_qmol(const char *input, size_t *mol_sz, const char *details_json)"
18
+ extern "char *get_molblock(const char *pkl, size_t pkl_sz, const char *details_json)"
19
+ extern "char *get_v3kmolblock(const char *pkl, size_t pkl_sz, const char *details_json)"
20
+ extern "char *get_smiles(const char *pkl, size_t pkl_sz, const char *details_json)"
21
+ extern "char *get_smarts(const char *pkl, size_t pkl_sz, const char *details_json)"
22
+ extern "char *get_cxsmiles(const char *pkl, size_t pkl_sz, const char *details_json)"
23
+ extern "char *get_cxsmarts(const char *pkl, size_t pkl_sz, const char *details_json)"
24
+ extern "char *get_json(const char *pkl, size_t pkl_sz, const char *details_json)"
25
+ extern "char *get_rxn(const char *input, size_t *mol_sz, const char *details_json)"
26
+ extern "char **get_mol_frags(const char *pkl, size_t pkl_sz, size_t **frags_pkl_sz_array, size_t *num_frags, const char *details_json, char **mappings_json)"
27
+
28
+ # substructure
29
+ extern "char *get_substruct_match(const char *mol_pkl, size_t mol_pkl_sz, const char *query_pkl, size_t query_pkl_sz, const char *options_json)"
30
+ extern "char *get_substruct_matches(const char *mol_pkl, size_t mol_pkl_sz, const char *query_pkl, size_t query_pkl_sz, const char *options_json)"
31
+
32
+ # drawing
33
+ extern "char *get_svg(const char *pkl, size_t pkl_sz, const char *details_json)"
34
+ extern "char *get_rxn_svg(const char *pkl, size_t pkl_sz, const char *details_json)"
35
+
36
+ # calculators
37
+ extern "char *get_descriptors(const char *pkl, size_t pkl_sz)"
38
+ extern "char *get_morgan_fp(const char *pkl, size_t pkl_sz, const char *details_json)"
39
+ extern "char *get_morgan_fp_as_bytes(const char *pkl, size_t pkl_sz, size_t *nbytes, const char *details_json)"
40
+ extern "char *get_rdkit_fp(const char *pkl, size_t pkl_sz, const char *details_json)"
41
+ extern "char *get_rdkit_fp_as_bytes(const char *pkl, size_t pkl_sz, size_t *nbytes, const char *details_json)"
42
+ extern "char *get_pattern_fp(const char *pkl, size_t pkl_sz, const char *details_json)"
43
+ extern "char *get_pattern_fp_as_bytes(const char *pkl, size_t pkl_sz, size_t *nbytes, const char *details_json)"
44
+ extern "char *get_topological_torsion_fp(const char *pkl, size_t pkl_sz, const char *details_json)"
45
+ extern "char *get_topological_torsion_fp_as_bytes(const char *pkl, size_t pkl_sz, size_t *nbytes, const char *details_json)"
46
+ extern "char *get_atom_pair_fp(const char *pkl, size_t pkl_sz, const char *details_json)"
47
+ extern "char *get_atom_pair_fp_as_bytes(const char *pkl, size_t pkl_sz, size_t *nbytes, const char *details_json)"
48
+ extern "char *get_maccs_fp(const char *pkl, size_t pkl_sz)"
49
+ extern "char *get_maccs_fp_as_bytes(const char *pkl, size_t pkl_sz, size_t *nbytes)"
50
+
51
+ # modification
52
+ extern "short add_hs(char **pkl, size_t *pkl_sz)"
53
+ extern "short remove_all_hs(char **pkl, size_t *pkl_sz)"
54
+
55
+ # standardization
56
+ extern "short cleanup(char **pkl, size_t *pkl_sz, const char *details_json)"
57
+ extern "short normalize(char **pkl, size_t *pkl_sz, const char *details_json)"
58
+ extern "short neutralize(char **pkl, size_t *pkl_sz, const char *details_json)"
59
+ extern "short reionize(char **pkl, size_t *pkl_sz, const char *details_json)"
60
+ extern "short canonical_tautomer(char **pkl, size_t *pkl_sz, const char *details_json)"
61
+ extern "short charge_parent(char **pkl, size_t *pkl_sz, const char *details_json)"
62
+ extern "short fragment_parent(char **pkl, size_t *pkl_sz, const char *details_json)"
63
+
64
+ # coordinates
65
+ extern "void prefer_coordgen(short val)"
66
+ extern "short has_coords(char *mol_pkl, size_t mol_pkl_sz)"
67
+ extern "short set_2d_coords(char **pkl, size_t *pkl_sz)"
68
+ extern "short set_3d_coords(char **pkl, size_t *pkl_sz, const char *params_json)"
69
+ extern "short set_2d_coords_aligned(char **pkl, size_t *pkl_sz, const char *template_pkl, size_t template_sz, const char *details_json, char **match_json)"
70
+
71
+ # housekeeping
72
+ # treat as void *ptr since calls free() internally
73
+ FREE = extern "void free_ptr(char *ptr)"
74
+
75
+ # other
76
+ extern "char *version()"
77
+ extern "void enable_logging()"
78
+ extern "void disable_logging()"
79
+
80
+ # chirality
81
+ extern "short use_legacy_stereo_perception(short value)"
82
+ extern "short allow_non_tetrahedral_chirality(short value)"
83
+
84
+ # logging
85
+ extern "void *set_log_tee(const char *log_name)"
86
+ extern "void *set_log_capture(const char *log_name)"
87
+ extern "short destroy_log_handle(void **log_handle)"
88
+ extern "char *get_log_buffer(void *log_handle)"
89
+ extern "short clear_log_buffer(void *log_handle)"
90
+ end
91
+ end
@@ -0,0 +1,340 @@
1
+ module RDKit
2
+ class Molecule
3
+ private_class_method :new
4
+
5
+ def self.from_smiles(input, **options)
6
+ mol = allocate
7
+ mol.send(:load_smiles, input, **options)
8
+ mol
9
+ end
10
+
11
+ def self.from_smarts(input)
12
+ mol = allocate
13
+ mol.send(:load_smarts, input)
14
+ mol
15
+ end
16
+
17
+ def num_atoms(only_explicit: true)
18
+ if only_explicit
19
+ json_data["molecules"].sum { |v| v["atoms"].count }
20
+ else
21
+ descriptors["NumAtoms"].to_i
22
+ end
23
+ end
24
+
25
+ def num_heavy_atoms
26
+ descriptors["NumHeavyAtoms"].to_i
27
+ end
28
+
29
+ def match?(pattern, use_chirality: true)
30
+ !match(pattern, use_chirality: use_chirality, max_matches: 1).nil?
31
+ end
32
+
33
+ def match(pattern, use_chirality: true, max_matches: nil)
34
+ check_pattern(pattern)
35
+
36
+ details = {
37
+ useChirality: use_chirality
38
+ }
39
+ details[:maxMatches] = max_matches.to_i if max_matches
40
+ json = check_string(FFI.get_substruct_matches(@ptr, @sz, pattern.instance_variable_get(:@ptr), pattern.instance_variable_get(:@sz), to_details(details)))
41
+
42
+ matches = JSON.parse(json)
43
+ if matches.any?
44
+ matches.map { |v| v["atoms"] }
45
+ end
46
+ end
47
+
48
+ def fragments(sanitize: true)
49
+ sz_arr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
50
+ num_frags = Fiddle::Pointer.malloc(Fiddle::SIZEOF_SIZE_T)
51
+ details = {
52
+ sanitizeFrags: sanitize
53
+ }
54
+ arr = FFI.get_mol_frags(@ptr, @sz, sz_arr.ref, num_frags.ref, to_details(details), nil)
55
+ check_ptr(arr)
56
+
57
+ num_frags.to_i.times.map do |i|
58
+ ptr = (arr + i * Fiddle::SIZEOF_VOIDP).ptr
59
+ sz = (sz_arr + i * Fiddle::SIZEOF_SIZE_T).ptr
60
+
61
+ mol = self.class.allocate
62
+ mol.send(:load_ptr, ptr, sz)
63
+ mol
64
+ end
65
+ ensure
66
+ free_ptr(sz_arr)
67
+ free_ptr(arr)
68
+ end
69
+
70
+ def rdkit_fingerprint(min_path: 1, max_path: 7, length: 2048, bits_per_hash: 2, use_hs: true, branched_paths: true, use_bond_order: true)
71
+ length = length.to_i
72
+ check_length(length)
73
+
74
+ details = {
75
+ minPath: min_path.to_i,
76
+ maxPath: max_path.to_i,
77
+ nBits: length,
78
+ nBitsPerHash: bits_per_hash.to_i,
79
+ useHs: use_hs,
80
+ branchedPaths: branched_paths,
81
+ useBondOrder: use_bond_order
82
+ }
83
+ check_string(FFI.get_rdkit_fp(@ptr, @sz, to_details(details)))
84
+ end
85
+
86
+ def morgan_fingerprint(radius: 3, length: 2048, use_chirality: false, use_bond_types: true)
87
+ radius = radius.to_i
88
+ if radius < 1
89
+ raise ArgumentError, "radius must be greater than 0"
90
+ end
91
+
92
+ length = length.to_i
93
+ check_length(length)
94
+
95
+ details = {
96
+ radius: radius,
97
+ nBits: length,
98
+ useChirality: use_chirality,
99
+ useBondTypes: use_bond_types
100
+ }
101
+ check_string(FFI.get_morgan_fp(@ptr, @sz, to_details(details)))
102
+ end
103
+
104
+ def pattern_fingerprint(length: 2048, tautomeric: false)
105
+ length = length.to_i
106
+ check_length(length)
107
+
108
+ details = {
109
+ nBits: length,
110
+ tautomericFingerprint: tautomeric
111
+ }
112
+ check_string(FFI.get_pattern_fp(@ptr, @sz, to_details(details)))
113
+ end
114
+
115
+ def topological_torsion_fingerprint(length: 2048)
116
+ length = length.to_i
117
+ check_length(length)
118
+
119
+ details = {
120
+ nBits: length
121
+ }
122
+ check_string(FFI.get_topological_torsion_fp(@ptr, @sz, to_details(details)))
123
+ end
124
+
125
+ def atom_pair_fingerprint(length: 2048, min_length: 1, max_length: 30)
126
+ length = length.to_i
127
+ check_length(length)
128
+
129
+ details = {
130
+ nBits: length,
131
+ minLength: min_length.to_i,
132
+ maxLength: max_length.to_i
133
+ }
134
+ check_string(FFI.get_atom_pair_fp(@ptr, @sz, to_details(details)))
135
+ end
136
+
137
+ def maccs_fingerprint
138
+ # remove bit 0 for correct length
139
+ # https://github.com/rdkit/rdkit/issues/1726
140
+ check_string(FFI.get_maccs_fp(@ptr, @sz))[1..]
141
+ end
142
+
143
+ def add_hs
144
+ dup.add_hs!
145
+ end
146
+
147
+ def add_hs!
148
+ check_status(FFI.add_hs(@ptr.ref, @sz.ref))
149
+ self
150
+ end
151
+
152
+ def remove_hs
153
+ dup.remove_hs!
154
+ end
155
+
156
+ def remove_hs!
157
+ check_status(FFI.remove_all_hs(@ptr.ref, @sz.ref))
158
+ self
159
+ end
160
+
161
+ def cleanup
162
+ dup.cleanup!
163
+ end
164
+
165
+ def cleanup!
166
+ check_status(FFI.cleanup(@ptr.ref, @sz.ref, to_details({})))
167
+ self
168
+ end
169
+
170
+ def normalize
171
+ dup.normalize!
172
+ end
173
+
174
+ def normalize!
175
+ check_status(FFI.normalize(@ptr.ref, @sz.ref, to_details({})))
176
+ self
177
+ end
178
+
179
+ def neutralize
180
+ dup.neutralize!
181
+ end
182
+
183
+ def neutralize!
184
+ check_status(FFI.neutralize(@ptr.ref, @sz.ref, to_details({})))
185
+ self
186
+ end
187
+
188
+ def reionize
189
+ dup.reionize!
190
+ end
191
+
192
+ def reionize!
193
+ check_status(FFI.reionize(@ptr.ref, @sz.ref, to_details({})))
194
+ self
195
+ end
196
+
197
+ def canonical_tautomer
198
+ dup.canonical_tautomer!
199
+ end
200
+
201
+ def canonical_tautomer!
202
+ check_status(FFI.canonical_tautomer(@ptr.ref, @sz.ref, to_details({})))
203
+ self
204
+ end
205
+
206
+ def charge_parent
207
+ dup.charge_parent!
208
+ end
209
+
210
+ def charge_parent!
211
+ check_status(FFI.charge_parent(@ptr.ref, @sz.ref, to_details({})))
212
+ self
213
+ end
214
+
215
+ def fragment_parent
216
+ dup.fragment_parent!
217
+ end
218
+
219
+ def fragment_parent!
220
+ check_status(FFI.fragment_parent(@ptr.ref, @sz.ref, to_details({})))
221
+ self
222
+ end
223
+
224
+ def to_smiles
225
+ check_string(FFI.get_smiles(@ptr, @sz, to_details({})))
226
+ end
227
+
228
+ def to_smarts
229
+ check_string(FFI.get_smarts(@ptr, @sz, to_details({})))
230
+ end
231
+
232
+ def to_cxsmiles
233
+ check_string(FFI.get_cxsmiles(@ptr, @sz, to_details({})))
234
+ end
235
+
236
+ def to_cxsmarts
237
+ check_string(FFI.get_cxsmarts(@ptr, @sz, to_details({})))
238
+ end
239
+
240
+ def to_json
241
+ check_string(FFI.get_json(@ptr, @sz, to_details({})))
242
+ end
243
+
244
+ def to_svg(width: 250, height: 200)
245
+ details = {
246
+ width: width,
247
+ height: height
248
+ }
249
+ check_string(FFI.get_svg(@ptr, @sz, to_details(details)))
250
+ end
251
+
252
+ def to_s
253
+ "#<#{self.class.name} #{to_smiles}>"
254
+ end
255
+ alias_method :inspect, :to_s
256
+
257
+ private
258
+
259
+ def initialize_copy(other)
260
+ super
261
+ load_smiles(other.to_smiles, sanitize: false, kekulize: false, remove_hs: false)
262
+ end
263
+
264
+ def load_smiles(input, sanitize: true, kekulize: true, remove_hs: true)
265
+ sz = Fiddle::Pointer.malloc(Fiddle::SIZEOF_SIZE_T)
266
+ details = {
267
+ sanitize: sanitize,
268
+ kekulize: kekulize,
269
+ removeHs: remove_hs
270
+ }
271
+ ptr = FFI.get_mol(input.to_str, sz.ref, to_details(details))
272
+ load_ptr(ptr, sz)
273
+ end
274
+
275
+ def load_smarts(input)
276
+ sz = Fiddle::Pointer.malloc(Fiddle::SIZEOF_SIZE_T)
277
+ ptr = FFI.get_qmol(input.to_str, sz.ref, to_details({}))
278
+ load_ptr(ptr, sz)
279
+ end
280
+
281
+ def load_ptr(ptr, sz)
282
+ if ptr.null?
283
+ raise ArgumentError, "invalid input"
284
+ end
285
+
286
+ @ptr = ptr
287
+ @ptr.free = FFI::FREE
288
+ @sz = sz
289
+ end
290
+
291
+ def to_details(options)
292
+ JSON.generate(options)
293
+ end
294
+
295
+ def json_data
296
+ JSON.parse(to_json)
297
+ end
298
+
299
+ def descriptors
300
+ JSON.parse(check_string(FFI.get_descriptors(@ptr, @sz)))
301
+ end
302
+
303
+ def check_pattern(pattern)
304
+ unless pattern.is_a?(self.class)
305
+ raise ArgumentError, "expected #{self.class.name}"
306
+ end
307
+ end
308
+
309
+ def check_length(length)
310
+ if length < 1
311
+ raise ArgumentError, "length must be greater than 0"
312
+ end
313
+ end
314
+
315
+ def check_ptr(ptr)
316
+ if ptr.nil? || ptr.null?
317
+ raise Error, "bad pointer"
318
+ end
319
+ end
320
+
321
+ def free_ptr(ptr)
322
+ FFI.free_ptr(ptr) if ptr
323
+ end
324
+
325
+ def check_string(ptr)
326
+ check_ptr(ptr)
327
+ begin
328
+ ptr.to_s
329
+ ensure
330
+ free_ptr(ptr)
331
+ end
332
+ end
333
+
334
+ def check_status(status)
335
+ if status != 1
336
+ raise Error, "bad status: #{status}"
337
+ end
338
+ end
339
+ end
340
+ end
@@ -0,0 +1,66 @@
1
+ module RDKit
2
+ class Reaction
3
+ private_class_method :new
4
+
5
+ def self.from_smarts(input)
6
+ rxn = allocate
7
+ rxn.send(:load_rxn, input)
8
+ rxn
9
+ end
10
+
11
+ def to_svg(width: 250, height: 200)
12
+ details = {
13
+ width: width,
14
+ height: height
15
+ }
16
+ check_string(FFI.get_rxn_svg(@ptr, @sz, to_details(details)))
17
+ end
18
+
19
+ def to_s
20
+ "#<#{self.class.name} #{@input}>"
21
+ end
22
+ alias_method :inspect, :to_s
23
+
24
+ private
25
+
26
+ def load_rxn(input)
27
+ sz = Fiddle::Pointer.malloc(Fiddle::SIZEOF_SIZE_T)
28
+ @input = input.to_str
29
+ ptr = FFI.get_rxn(@input, sz.ref, to_details({}))
30
+ load_ptr(ptr, sz)
31
+ end
32
+
33
+ def load_ptr(ptr, sz)
34
+ if ptr.null?
35
+ raise ArgumentError, "invalid input"
36
+ end
37
+
38
+ @ptr = ptr
39
+ @ptr.free = FFI::FREE
40
+ @sz = sz
41
+ end
42
+
43
+ def to_details(options)
44
+ JSON.generate(options)
45
+ end
46
+
47
+ def check_ptr(ptr)
48
+ if ptr.nil? || ptr.null?
49
+ raise Error, "bad pointer"
50
+ end
51
+ end
52
+
53
+ def free_ptr(ptr)
54
+ FFI.free_ptr(ptr) if ptr
55
+ end
56
+
57
+ def check_string(ptr)
58
+ check_ptr(ptr)
59
+ begin
60
+ ptr.to_s
61
+ ensure
62
+ free_ptr(ptr)
63
+ end
64
+ end
65
+ end
66
+ end