rdkit-rb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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