rubabel 0.1.6 → 0.2.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.6
1
+ 0.2.0
@@ -79,7 +79,6 @@ module Rubabel
79
79
  end
80
80
  end
81
81
 
82
- alias_method :each, :each_bond
83
82
 
84
83
  # retrieves the bond
85
84
  def get_bond(atom)
@@ -101,6 +100,7 @@ module Rubabel
101
100
  _atom = @ob.next_nbr_atom(iter)
102
101
  end
103
102
  end
103
+ alias_method :each, :each_atom
104
104
 
105
105
  # returns the neighboring atoms. Consider using each_atom.
106
106
  def atoms
@@ -153,6 +153,79 @@ module Rubabel
153
153
  @ob.get_partial_charge
154
154
  end
155
155
 
156
+ # permanently removes a hydrogen by properly incrementing the
157
+ # spin_multiplicity (and deleting a hydrogen if one is explicitly attached
158
+ # to the atom). If called with cnt=2 a carbene or nitrene can be made
159
+ # (giving a spin_multiplicity of 3). Makes no effort to ensure that the
160
+ # proper number of hydrogens already exist to be deleted, just alters the
161
+ # spin_multiplicity and deletes the right number of hydrogens if they are
162
+ # available to be deleted. Adds one charge to the atom.
163
+ def remove_an_h!(add_charge=true)
164
+ new_spin =
165
+ case @ob.get_spin_multiplicity
166
+ when 0 then 2
167
+ when 2 then 3
168
+ end
169
+ @ob.set_spin_multiplicity(new_spin)
170
+ atoms.each do |atom|
171
+ if atom.atomic_num == 1
172
+ self.mol.delete_atom(atom)
173
+ break
174
+ end
175
+ end
176
+ # add the charge
177
+ (self.charge = charge + 1) if add_charge
178
+ self
179
+ end
180
+
181
+ # philosophy on equality: there are *so* many ways for two atoms to be
182
+ # different that we can never really ensure that "equivalence" is met
183
+ # without calling ~20 methods. We narrowly define equivalence so it is
184
+ # useful for that case and let the user make more complicated
185
+ # equivalency/equality definitions themselves.
186
+
187
+ # the exact same atom in the same molecule. The equivalency test for
188
+ # molecules is a little pricey, so better to use something like atom.id ==
189
+ # other.id if you know you are working within the same molecule.
190
+ def equal?(other)
191
+ other.respond_to?(:mol) && mol.equal?(other.mol) && id == other.id
192
+ end
193
+
194
+ alias_method :==, :equal?
195
+ alias_method :eql?, :equal?
196
+
197
+ ## opposite of remove_an_h!
198
+ ## THIS IS STILL BROKEN!!!
199
+ # maybe need to change the type?? C+ -> C2 or C3, but this gets really
200
+ # invasive... Why is this so flippin hard to do!!
201
+ #def add_an_h!(remove_charge=true)
202
+ #new_spin =
203
+ #case @ob.get_spin_multiplicity
204
+ #when 2 then 0
205
+ #when [1,3] then 2
206
+ #end
207
+ #@ob.set_spin_multiplicity(new_spin)
208
+
209
+ #puts self.inspect_internals
210
+ #puts "EXAMIN B:"
211
+ #p self
212
+ #p self.charge
213
+ #(self.charge = self.charge - 1) if remove_charge
214
+ #puts "EXAMIN A:"
215
+ #puts self.inspect_internals
216
+ #p self
217
+ #p self.charge
218
+ #puts "BEFORE:"
219
+ #p mol.formula
220
+ #p mol.atoms
221
+ #mol.add_bond!(self, mol.add_atom!(1))
222
+ #puts "AFTER:"
223
+ #p mol.formula
224
+ #p mol.atoms
225
+ #abort 'here'
226
+ #self
227
+ #end
228
+
156
229
  def spin
157
230
  @ob.get_spin_multiplicity
158
231
  end
@@ -203,8 +276,25 @@ module Rubabel
203
276
  def hbond_donor?() @ob.is_hbond_donor end
204
277
  def hbond_donor_h?() @ob.is_hbond_donor_h end
205
278
 
279
+ def double_bond?
280
+ each_bond.any? {|bond| bond.bond_order == 2 }
281
+ end
282
+
283
+ def single_bond?
284
+ each_bond.any? {|bond| bond.bond_order == 1 }
285
+ end
286
+
206
287
  def carboxyl_carbon?
207
- atoms.any?(&:carboxyl_oxygen?)
288
+ each_atom.any?(&:carboxyl_oxygen?)
289
+ end
290
+
291
+ def carbonyl_oxygen?
292
+ ats = atoms
293
+ ats.size == 1 && ats.first.el == :c && double_bond?
294
+ end
295
+
296
+ def carbonyl_carbon?
297
+ each_atom.any?(&:carbonyl_oxygen?)
208
298
  end
209
299
 
210
300
  # # does this carbon hold a primary alcohol
@@ -218,5 +308,17 @@ module Rubabel
218
308
  def inspect
219
309
  "<#{type} id:#{id}>"
220
310
  end
311
+
312
+ def inspect_internals
313
+ "<" << @ob.methods.grep(/get_/).map do |mthd|
314
+ begin
315
+ "#{mthd.to_s.sub(/get_/,'')}=#{@ob.send(mthd)}"
316
+ rescue ArgumentError
317
+ nil
318
+ end
319
+ end.compact.join(" ") << ">"
320
+ end
321
+
322
+
221
323
  end
222
324
  end
@@ -63,6 +63,25 @@ module Rubabel
63
63
  @ob.set_bond_order(val)
64
64
  end
65
65
 
66
+ # returns self
67
+ def set_atoms!(beg_atom, end_atom)
68
+ @ob.set_begin(beg_atom.ob)
69
+ @ob.set_end(end_atom.ob)
70
+ self
71
+ end
72
+
73
+ # Sets the beginning atom of the bond to atom. returns self
74
+ def set_begin!(atom)
75
+ @ob.set_begin(atom.ob)
76
+ self
77
+ end
78
+
79
+ # Sets the end atom of the bond to the given atom. returns self
80
+ def set_end!(atom)
81
+ @ob.set_end(atom.ob)
82
+ self
83
+ end
84
+
66
85
  # returns an array of Rubabel::Atoms
67
86
  def atoms
68
87
  [@ob.get_begin_atom.upcast, @ob.get_end_atom.upcast]
@@ -112,9 +112,10 @@ module Rubabel
112
112
  def add_atom!(atomic_num=1)
113
113
  # jtp implementation:
114
114
  # @ob.add_atom(atom.ob)
115
- new_a = @ob.new_atom
116
- new_a.set_atomic_num(atomic_num)
117
- Rubabel::Atom.new(new_a)
115
+ new_obatom = @ob.new_atom
116
+ new_obatom.set_atomic_num(atomic_num)
117
+ #@ob.add_atom(new_obatom)
118
+ Rubabel::Atom.new(new_obatom)
118
119
  end
119
120
 
120
121
  def delete_atom(atom)
@@ -144,7 +145,6 @@ module Rubabel
144
145
  # to add_h!
145
146
  def formula() @ob.get_formula end
146
147
 
147
-
148
148
  def initialize(obmol)
149
149
  @ob = obmol
150
150
  end
@@ -254,11 +254,6 @@ module Rubabel
254
254
  self
255
255
  end
256
256
 
257
- # calls separate on the OBMol object
258
- def separate!
259
- @ob.separate
260
- end
261
-
262
257
  # returns just the smiles string :smi (not the id)
263
258
  def smiles
264
259
  to_s(:smi)
@@ -269,10 +264,38 @@ module Rubabel
269
264
  to_s(:can)
270
265
  end
271
266
 
272
- # equal if their canonical SMILES strings (including ID) are equal. This
273
- # may become more rigorous in the future
267
+ # checks to see if the molecules are the same OBMol object underneath by
268
+ # modifying one and seeing if the other changes. This is because
269
+ # openbabel routinely creates new objects that point to the same
270
+ # underlying data store, so even checking for OBMol equivalency is not
271
+ # enough.
272
+ def equal?(other)
273
+ return false unless other.is_a?(self.class)
274
+ are_identical = false
275
+ if self.title == other.title
276
+ begin
277
+ obj_id = self.object_id.to_s
278
+ self.title += obj_id
279
+ are_identical = (self.title == other.title)
280
+ ensure
281
+ self.title.sub(/#{obj_id}$/,'')
282
+ end
283
+ are_identical
284
+ else
285
+ false
286
+ end
287
+ end
288
+
289
+ alias_method :eql?, :equal?
290
+
291
+ # defined as whether the csmiles strings are identical. This incorporates
292
+ # more information than the FP2 fingerprint, for instance (try changing
293
+ # the charge and see how it does not influence the fingerprint).
294
+ # Obviously, things like title or data will not be evaluated with ==. See
295
+ # equal? if you are looking for identity. More stringent comparisons will
296
+ # have to be done by hand!
274
297
  def ==(other)
275
- self.write_string(:can) == other.write_string(:can)
298
+ other.respond_to?(:csmiles) && (csmiles == other.csmiles)
276
299
  end
277
300
 
278
301
  # iterates over the molecule's Rubabel::Atom objects
@@ -308,11 +331,21 @@ module Rubabel
308
331
  self
309
332
  end
310
333
 
334
+ # gets the bond by id
335
+ def bond(id)
336
+ @ob.get_bond_by_id(id).upcast
337
+ end
338
+
311
339
  # returns the array of bonds. Consider using #each_bond
312
340
  def bonds
313
341
  each_bond.map.to_a
314
342
  end
315
343
 
344
+ # gets the atom by id
345
+ def atom(id)
346
+ @ob.get_atom_by_id(id).upcast
347
+ end
348
+
316
349
  # returns the array of atoms. Consider using #each
317
350
  def atoms
318
351
  each_atom.map.to_a
@@ -358,8 +391,15 @@ module Rubabel
358
391
  end
359
392
  end
360
393
 
361
- def delete_bond(bond)
362
- @ob.delete_bond(bond.ob, false)
394
+ # if given a bond, deletes it (doesn't garbage collect). If given two
395
+ # atoms, deletes the bond between them.
396
+ def delete_bond(*args)
397
+ case args.size
398
+ when 1
399
+ @ob.delete_bond(args[0].ob, false)
400
+ when 2
401
+ @ob.delete_bond(args[0].get_bond(args[1]).ob, false)
402
+ end
363
403
  end
364
404
 
365
405
  def delete_atom(atom)
@@ -373,21 +413,15 @@ module Rubabel
373
413
  self
374
414
  end
375
415
 
376
- # takes a Rubabel::Bond object or a pair of Rubabel::Atom objects
377
- def add_bond!(*args)
378
- case args.size
379
- when 1
380
- ob_bond = args.first.ob
381
- @ob.add_bond(ob_bond)
382
- ob_bond.get_begin_atom.add_bond(ob_bond)
383
- ob_bond.get_end_atom.add_bond(ob_bond)
384
- when 2
385
- @ob.add_bond(args[0].idx, args[1].idx, args[2] || 1)
386
- #ob_bond = Rubabel::Bond[ *args ].ob
387
- #ob_bond.get_begin_atom.add_bond(ob_bond)
388
- #ob_bond.get_end_atom.add_bond(ob_bond)
389
- #@ob.add_bond(ob_bond)
390
- end
416
+ # creates a new (as yet unspecified) bond associated with the molecule and gives it a unique id
417
+ def new_bond
418
+ @ob.new_bond.upcast
419
+ end
420
+
421
+ # takes a pair of Rubabel::Atom objects and adds a bond to the molecule
422
+ # returns whether the bond creation was successful.
423
+ def add_bond!(atom1, atom2, order=1)
424
+ @ob.add_bond(atom1.idx, atom2.idx, order)
391
425
  end
392
426
 
393
427
  # yields self after deleting the specified bonds. When the block is
@@ -405,14 +439,24 @@ module Rubabel
405
439
  end
406
440
 
407
441
  # splits the molecules at the given bonds and returns the fragments. Does
408
- # not alter the caller.
442
+ # not alter the caller. If the molecule is already fragmented, then
443
+ # returns the separate fragments.
409
444
  def split(*bonds)
410
- delete_and_restore_bonds(*bonds) do |mol|
411
- mol.ob.separate.map(&:upcast)
445
+ if bonds.size > 0
446
+ delete_and_restore_bonds(*bonds) do |mol|
447
+ mol.ob.separate.map(&:upcast)
448
+ end
449
+ else
450
+ self.ob.separate.map(&:upcast)
412
451
  end
413
452
  end
414
453
 
415
- alias_method :separate, :split
454
+ def each_fragment(&block)
455
+ block or return enum_for(__method__)
456
+ @ob.separate.each do |ob_mol|
457
+ block.call( ob_mol.upcast )
458
+ end
459
+ end
416
460
 
417
461
  # emits smiles without the trailing tab, newline, or id. Use write_string
418
462
  # to get the default OpenBabel behavior (ie., tabs and newlines).
@@ -593,6 +637,21 @@ module Rubabel
593
637
  distance_matrix.max
594
638
  end
595
639
 
640
+ # adds 1 hydrogen to the formula and returns self
641
+ def add_hydrogen_to_formula!
642
+ string = @ob.get_formula
643
+ substituted = false
644
+ new_string = string.sub(/H(\d*)/) { substituted=true; "H#{$1.to_i+1}" }
645
+ unless substituted
646
+ new_string = string.sub("^(C?\d*)") { $1 + 'H' }
647
+ end
648
+ puts 'HERE'
649
+ p string
650
+ p new_string
651
+ #@ob.set_formula(new_string)
652
+ self
653
+ end
654
+
596
655
  end
597
656
  end
598
657
 
@@ -7,18 +7,20 @@ module Rubabel
7
7
  module Fragmentable
8
8
 
9
9
  #:sp3c_oxygen_asymmetric_far_sp3, :sp3c_nitrogen_asymmetric_far_sp3,
10
- RULES = Set[ :alcohol_to_aldehyde, :peroxy_to_carboxy, :co2_loss,
11
- :sp3c_oxygen_double_bond_far_side_sp3, :sp3c_oxygen_double_bond_far_side_sp2, :sp3c_oxygen_double_bond_water_loss, :sp3c_nitrogen_double_bond,
12
- ]
10
+ #RULES = Set[ :alcohol_to_aldehyde, :peroxy_to_carboxy, :co2_loss,
11
+ # :sp3c_oxygen_double_bond_far_side_sp3, :sp3c_oxygen_double_bond_far_side_sp2, :sp3c_oxygen_double_bond_water_loss, :sp3c_nitrogen_double_bond,
12
+ #]
13
13
  #ADDUCTS = [:lioh, :nh4cl, :nh4oh]
14
- CO_RULES = Set[:alcohol_to_aldehyde, :peroxy_to_carboxy, :co2_loss,
15
- :sp3c_oxygen_double_bond_water_loss, :sp3c_oxygen_double_bond_far_side_sp2, :sp3c_oxygen_double_bond_far_side_sp3, :sp3c_oxygen_asymmetric_far_sp3
16
- ]
14
+ #CO_RULES = Set[:alcohol_to_aldehyde, :peroxy_to_carboxy, :co2_loss,
15
+ # :sp3c_oxygen_double_bond_water_loss, :sp3c_oxygen_double_bond_far_side_sp2, :sp3c_oxygen_double_bond_far_side_sp3, :sp3c_oxygen_asymmetric_far_sp3
16
+ #]
17
+
18
+ RULES = Set[:cad_o, :cad_oo, :oxed_ether]
17
19
 
18
20
  DEFAULT_OPTIONS = {
19
21
  rules: RULES,
20
22
  #adduct: nil,
21
- ph: 7.4,
23
+ #ph: 7.4,
22
24
  # return only the set of unique fragments
23
25
  uniq: false,
24
26
  }
@@ -142,98 +144,90 @@ module Rubabel
142
144
 
143
145
  end
144
146
 
145
- # to ensure proper fragmentation, will add_h!(ph) first at the given ph
147
+ # splits the molecule between the carbon and carbon_nbr, adds a double
148
+ # bond between the carbon and oxygen, and moves whatever was on the
149
+ # oxygen (e.g., an OH or a charge) to the carbon_nbr. Returns two new
150
+ # molecules.
151
+ def carbonyl_oxygen_dump(carbon, oxygen, carbon_nbr)
152
+ appendage = oxygen.atoms.find {|a| a.el != :c }
153
+ if oxygen.charge != 0
154
+ ocharge = oxygen.charge
155
+ end
156
+ nmol = self.dup
157
+ new_oxygen = nmol.atom(oxygen.id)
158
+ new_carbon = nmol.atom(carbon.id)
159
+ new_carbon_nbr = nmol.atom(carbon_nbr.id)
160
+ new_appendage = nmol.atom(appendage.id) if appendage
161
+ nmol.delete_bond(new_carbon.get_bond(new_carbon_nbr))
162
+ if new_appendage
163
+ nmol.delete_bond(new_oxygen.get_bond(new_appendage))
164
+ nmol.add_bond!(new_carbon_nbr, new_appendage)
165
+ end
166
+ if ocharge
167
+ new_carbon_nbr.charge += ocharge
168
+ new_oxygen.charge -= ocharge
169
+ end
170
+ new_carbon.get_bond(new_oxygen).bond_order = 2
171
+ nmol.split
172
+ end
173
+
174
+ # breaks the bond and gives the electrons to the oxygen
175
+ def carbon_oxygen_esteal(carbon, oxygen)
176
+ nmol = self.dup
177
+ nmol.ob.add_hydrogens
178
+ ncarbon = nmol.atom(carbon.id)
179
+ noxygen = nmol.atom(oxygen.id)
180
+ nmol.delete_bond(ncarbon, noxygen)
181
+ ncarbon.charge += 1
182
+ noxygen.charge -= 1
183
+ ncarbon.remove_an_h!
184
+ #p ncarbon.ob.implicit_hydrogen_count
185
+ #p ncarbon
186
+ #ncarbon.ob.decrement_implicit_valence
187
+ #p ncarbon.ob.implicit_hydrogen_count
188
+ #p ncarbon
189
+ #ncarbon.ob.increment_implicit_valence
190
+
191
+ nmol.title = nmol.to_s
192
+ p nmol.write("tmp.svg")
193
+ parts = nmol.split
194
+ p z=parts.first
195
+ p z.formula
196
+ p z.mass
197
+ p z.exact_mass
198
+
199
+ puts "HIAY"
200
+ end
201
+
146
202
  # an empty array is returned if there are no fragments generated.
203
+ # Hydrogens are added at a pH of 7.4, unless they have already been
204
+ # added.
147
205
  #
148
- # :ph => 7.4
149
- # :uniq => false
206
+ # :rules => queryable by :include? set of rules
207
+ # :uniq => false
150
208
  def fragment(opts={})
209
+ only_uniqs = true
151
210
  opts = DEFAULT_OPTIONS.merge(opts)
152
211
  opts[:rules].each do |rule|
153
212
  raise ArgumentError, "bad rule: #{rule}" unless RULES.include?(rule)
154
213
  end
155
214
 
156
215
  had_hydrogens = self.h_added?
157
-
158
- self.correct_for_ph!(opts[:ph])
216
+ self.correct_for_ph!(7.4) unless had_hydrogens
159
217
  self.remove_h!
160
218
 
161
- rules = opts[:rules]
162
219
  fragment_sets = []
163
- if rules.any? {|rule| CO_RULES.include?(rule) }
164
- putsv "matching C-O"
165
- self.each_match("CO").each do |_atoms|
166
- # note: this will *not* match C=O
167
- (carbon, oxygen) = _atoms
168
- carbon_nbrs = carbon.atoms.reject {|atom| atom == oxygen }
169
- c3_nbrs = carbon_nbrs.select {|atm| atm.type == 'C3' }
170
- # pulling this out here causes it to work incorrectly internally
171
- # (not sure why)
172
- #co_bond = carbon.get_bond(oxygen)
173
-
174
- case oxygen.bonds.size # non-hydrogen bonds
175
- when 1 # *must* be an alcohol or a carboxylic acid
176
- putsv "#{csmiles} oxygen has no other bonds besides C-O (alcohol or carboxylic acid)"
177
- if carbon.type == 'C3'
178
- if rules.include?(:sp3c_oxygen_double_bond_water_loss)
179
- putsv "rule :sp3c_oxygen_double_bond_water_loss"
180
- fragment_sets.push *near_side_double_bond_break(carbon, oxygen)
181
- end
182
- if rules.include?(:alcohol_to_aldehyde)
183
- putsv "rule :alcohol_to_aldehyde"
184
- fragment_sets.push *alcohol_to_aldehyde(carbon, oxygen, carbon_nbrs)
185
- end
186
- elsif carbon.carboxyl_carbon?
187
- if rules.include?(:co2_loss)
188
- putsv "rule :co2_loss"
189
- if c3_nbr = c3_nbrs.first
190
- fragment_sets.push *co2_loss(carbon, oxygen, c3_nbr)
191
- end
192
- end
193
- end
194
- when 2
195
- putsv "#{csmiles} c-o & oxygen has 2 non-hydrogen bonds"
196
- oxygen_nbr = oxygen.atoms.reject {|atom| atom.idx == carbon.idx }.first
197
- if carbon.type == 'C3'
198
- if rules.include?(:peroxy_to_carboxy)
199
- fragment_sets.push *peroxy_to_carboxy(carbon, oxygen, carbon_nbrs, oxygen_nbr)
200
- end
201
- # ester and ethers (look *only* on close side for places to make
202
- # double bond)
203
-
204
- if oxygen_nbr.type == 'C3'
205
- putsv "oxygen nbr is C3"
206
- if rules.include?(:sp3c_oxygen_double_bond_far_side_sp3)
207
- putsv "rule :sp3c_oxygen_double_bond_far_side_sp3"
208
- fragment_sets.push *near_side_double_bond_break(carbon, oxygen)
209
- end
210
- if rules.include?(:sp3c_oxygen_asymmetric_far_sp3)
211
- putsv "rule :sp3c_oxygen_asymmetric_far_sp3"
212
- # only returns a single frag set
213
- fragment_sets.push electrophile_snatches_electrons(carbon, oxygen)
214
- end
215
- end
216
- if oxygen_nbr.type == 'C2'
217
- if rules.include?(:sp3c_oxygen_double_bond_far_side_sp2)
218
- putsv "rule :sp3c_oxygen_double_bond_far_side_sp2"
219
- fragment_sets.push *near_side_double_bond_break(carbon, oxygen)
220
- end
221
- end
222
- # note: the case of a carboxy is found with CO search
223
- end
220
+
221
+ if opts[:rules].any? {|r| [:cad_o, :cad_oo].include?(r) }
222
+ self.each_match("C[O;h1,O]", only_uniqs) do |carbon, oxygen|
223
+ carbon.atoms.select {|a| a.el == :c }.each do |carbon_nbr|
224
+ fragment_sets << carbonyl_oxygen_dump(carbon, oxygen, carbon_nbr)
224
225
  end
225
226
  end
226
227
  end
227
- if rules.include?(:sp3c_nitrogen_double_bond)
228
- self.each_match("CN") do |_atoms|
229
- (carbon, nitrogen) = _atoms
230
- num_nitrogen_bonds = nitrogen.bonds.size
231
- case num_nitrogen_bonds
232
- when 2
233
- if carbon.type == 'C3'
234
- fragment_sets.push *near_side_double_bond_break(carbon, nitrogen)
235
- end
236
- end
228
+ if opts[:rules].any? {|r| [:oxed_ether].include?(r) }
229
+ self.each_match("C[O&X2]", only_uniqs) do |carbon, oxygen|
230
+ fragment_sets << carbon_oxygen_esteal(carbon, oxygen)
237
231
  end
238
232
  end
239
233
 
@@ -243,12 +237,110 @@ module Rubabel
243
237
  end
244
238
  if opts[:uniq]
245
239
  # TODO: impelent properly
246
- #fragment_sets = fragment_sets.uniq_by(&:csmiles)
247
240
  raise NotImplementedError
241
+ #fragment_sets = fragment_sets.uniq_by(&:csmiles)
248
242
  end
243
+
249
244
  fragment_sets
250
245
  end
251
246
 
247
+
248
+ # had_hydrogens = self.h_added?
249
+
250
+ #self.correct_for_ph!(opts[:ph])
251
+ #self.remove_h!
252
+
253
+ #rules = opts[:rules]
254
+ #fragment_sets = []
255
+ #if rules.any? {|rule| CO_RULES.include?(rule) }
256
+ #putsv "matching C-O"
257
+ #self.each_match("CO").each do |_atoms|
258
+ ## note: this will *not* match C=O
259
+ #(carbon, oxygen) = _atoms
260
+ #carbon_nbrs = carbon.atoms.reject {|atom| atom == oxygen }
261
+ #c3_nbrs = carbon_nbrs.select {|atm| atm.type == 'C3' }
262
+ ## pulling this out here causes it to work incorrectly internally
263
+ ## (not sure why)
264
+ ##co_bond = carbon.get_bond(oxygen)
265
+
266
+ #case oxygen.bonds.size # non-hydrogen bonds
267
+ #when 1 # *must* be an alcohol or a carboxylic acid
268
+ #putsv "#{csmiles} oxygen has no other bonds besides C-O (alcohol or carboxylic acid)"
269
+ #if carbon.type == 'C3'
270
+ #if rules.include?(:sp3c_oxygen_double_bond_water_loss)
271
+ #putsv "rule :sp3c_oxygen_double_bond_water_loss"
272
+ #fragment_sets.push *near_side_double_bond_break(carbon, oxygen)
273
+ #end
274
+ #if rules.include?(:alcohol_to_aldehyde)
275
+ #putsv "rule :alcohol_to_aldehyde"
276
+ #fragment_sets.push *alcohol_to_aldehyde(carbon, oxygen, carbon_nbrs)
277
+ #end
278
+ #elsif carbon.carboxyl_carbon?
279
+ #if rules.include?(:co2_loss)
280
+ #putsv "rule :co2_loss"
281
+ #if c3_nbr = c3_nbrs.first
282
+ #fragment_sets.push *co2_loss(carbon, oxygen, c3_nbr)
283
+ #end
284
+ #end
285
+ #end
286
+ #when 2
287
+ #putsv "#{csmiles} c-o & oxygen has 2 non-hydrogen bonds"
288
+ #oxygen_nbr = oxygen.atoms.reject {|atom| atom.idx == carbon.idx }.first
289
+ #if carbon.type == 'C3'
290
+ #if rules.include?(:peroxy_to_carboxy)
291
+ #fragment_sets.push *peroxy_to_carboxy(carbon, oxygen, carbon_nbrs, oxygen_nbr)
292
+ #end
293
+ ## ester and ethers (look *only* on close side for places to make
294
+ ## double bond)
295
+
296
+ #if oxygen_nbr.type == 'C3'
297
+ #putsv "oxygen nbr is C3"
298
+ #if rules.include?(:sp3c_oxygen_double_bond_far_side_sp3)
299
+ #putsv "rule :sp3c_oxygen_double_bond_far_side_sp3"
300
+ #fragment_sets.push *near_side_double_bond_break(carbon, oxygen)
301
+ #end
302
+ #if rules.include?(:sp3c_oxygen_asymmetric_far_sp3)
303
+ #putsv "rule :sp3c_oxygen_asymmetric_far_sp3"
304
+ ## only returns a single frag set
305
+ #fragment_sets.push electrophile_snatches_electrons(carbon, oxygen)
306
+ #end
307
+ #end
308
+ #if oxygen_nbr.type == 'C2'
309
+ #if rules.include?(:sp3c_oxygen_double_bond_far_side_sp2)
310
+ #putsv "rule :sp3c_oxygen_double_bond_far_side_sp2"
311
+ #fragment_sets.push *near_side_double_bond_break(carbon, oxygen)
312
+ #end
313
+ #end
314
+ ## note: the case of a carboxy is found with CO search
315
+ #end
316
+ #end
317
+ #end
318
+ #end
319
+ #if rules.include?(:sp3c_nitrogen_double_bond)
320
+ #self.each_match("CN") do |_atoms|
321
+ #(carbon, nitrogen) = _atoms
322
+ #num_nitrogen_bonds = nitrogen.bonds.size
323
+ #case num_nitrogen_bonds
324
+ #when 2
325
+ #if carbon.type == 'C3'
326
+ #fragment_sets.push *near_side_double_bond_break(carbon, nitrogen)
327
+ #end
328
+ #end
329
+ #end
330
+ #end
331
+
332
+ #unless had_hydrogens
333
+ #fragment_sets.each {|set| set.each(&:remove_h!) }
334
+ #self.remove_h!
335
+ #end
336
+ #if opts[:uniq]
337
+ ## TODO: impelent properly
338
+ ##fragment_sets = fragment_sets.uniq_by(&:csmiles)
339
+ #raise NotImplementedError
340
+ #end
341
+ #fragment_sets
342
+ #end
343
+
252
344
  end
253
345
  include Fragmentable
254
346
  end
@@ -17,6 +17,45 @@ describe Rubabel::Atom do
17
17
  chlorine.id.should == 3
18
18
  end
19
19
 
20
+ specify 'equality' do
21
+ mol = Rubabel["CCO"]
22
+ oxygen = mol.atoms[2]
23
+ oxygen_from_match = mol.matches("CO").first.last
24
+ (oxygen == oxygen_from_match).should be_true
25
+ (oxygen.equal?(oxygen_from_match)).should be_true
26
+ (oxygen.eql?(oxygen_from_match)).should be_true
27
+
28
+ mol2 = Rubabel["CCO"]
29
+ (mol.atoms[0] == mol2.atoms[0]).should_not be_true
30
+ (mol.atoms[0].equal?(mol2.atoms[0])).should_not be_true
31
+ (mol.atoms[0].eql?(mol2.atoms[0])).should_not be_true
32
+ end
33
+
34
+ it 'removes hydrogens with proper charge accounting' do
35
+ mol = Rubabel["CC"]
36
+ mol.add_h!
37
+ mol.atoms[0].remove_an_h!
38
+ mol.formula.should == "C2H5"
39
+ mol.csmiles.should == 'C[CH2+]'
40
+ mol.exact_mass.round(5).should == 29.03913
41
+ mol.charge.should == 1
42
+
43
+ # can't seem to get working properly!!!
44
+ #mol.atoms[0].add_an_h!
45
+ #mol.formula.should == 'C2H6'
46
+ #mol.csmiles.should == 'CC'
47
+ #mol.charge.should == 0
48
+ ##fmol.atoms[0].charge -= 1
49
+ #mol.exact_mass.should == 323
50
+ end
51
+
52
+ it 'can find atom identities with simple questions' do
53
+ mol = Rubabel["NCC(O)CC(=O)"]
54
+ (c_exp, o_exp) = mol.matches("C=O").first
55
+ mol.find(&:carbonyl_carbon?).id.should == c_exp.id
56
+ mol.find(&:carbonyl_oxygen?).id.should == o_exp.id
57
+ end
58
+
20
59
  describe 'working with a complex molecule' do
21
60
 
22
61
  before do
@@ -4,16 +4,19 @@ require 'rubabel/molecule'
4
4
  require 'rubabel/bond'
5
5
 
6
6
  describe Rubabel::Bond do
7
- subject { Rubabel::Molecule.from_file( TESTFILES + '/cholesterol.sdf' ).bonds.first }
7
+ describe 'cholesterol from sdf' do
8
+ subject { Rubabel::Molecule.from_file( TESTFILES + '/cholesterol.sdf' ).bonds.first }
8
9
 
9
- it 'is a Rubabel::Bond' do
10
- subject.should be_a(Rubabel::Bond)
11
- end
10
+ it 'is a Rubabel::Bond' do
11
+ subject.should be_a(Rubabel::Bond)
12
+ end
12
13
 
13
- it 'knows what atoms it includes' do
14
- subject.each_atom do |atom|
15
- atom.should be_a(Rubabel::Atom)
14
+ it 'knows what atoms it includes' do
15
+ subject.each_atom do |atom|
16
+ atom.should be_a(Rubabel::Atom)
17
+ end
18
+ subject.atoms.size.should == 2
16
19
  end
17
- subject.atoms.size.should == 2
18
20
  end
21
+
19
22
  end
@@ -4,139 +4,226 @@ require 'rubabel'
4
4
 
5
5
  $VERBOSE = nil
6
6
 
7
- describe Rubabel::Molecule::Fragmentable do
8
-
9
- # :peroxy_to_carboxy
10
- # :oxygen_asymmetric_sp3, :nitrogen_asymmetric_sp3,
11
- # :internal_phosphoester
12
-
13
- describe 'fragmentation rules' do
14
- # coenzyme: CC1=CC(=O)C=CC1=O
15
- # 2-methylcyclohexa-2,5-diene-1,4-dione
16
-
17
- let(:test_mol) { "COP(=O)(O)OCNCOCC(OO)C(=O)O" }
18
-
19
- it 'raises an error for a bad rule' do
20
- mol = Rubabel["CCNC"]
21
- expect { mol.fragment(rules: [:wackiness]) }.to raise_error
22
- end
23
-
24
- describe ':sp3c_nitrogen_double_bond' do
25
-
26
- it 'cleaves like an ether a secondary NH group if possible' do
27
- mol = Rubabel["CCNC"]
28
- frag_sets = mol.fragment(rules: [:sp3c_nitrogen_double_bond])
29
- frag_sets.size.should == 1
30
- csmiles = frag_sets.first.map(&:csmiles)
31
- csmiles.should include("C=C")
32
- csmiles.should include("C[NH3+]")
33
- end
34
-
35
- it 'will not cleave if not possible' do
36
- mol = Rubabel["CNC"]
37
- frag_sets = mol.fragment(rules: [:sp3c_nitrogen_double_bond])
38
- frag_sets.should be_empty
39
- end
40
-
41
- end
42
-
43
- describe ':co2_loss' do
44
- it 'loss of CO2 from carboxy group with charge transfer' do
45
- mol = Rubabel["NCC(=O)O"]
46
- frag_sets = mol.fragment( rules: [:co2_loss] )
47
- frag_sets.size.should == 1
48
- csmiles = frag_sets.first.map(&:csmiles)
49
-
50
- csmiles.should include("[CH2-][NH3+]")
51
- csmiles.should include("O=C=O")
52
- end
53
-
54
- it "doesn't remove CO2 if adjacent is not c3" do
55
- mol = Rubabel["C=CC(=O)O"]
56
- fragments = mol.fragment( rules: [:co2_loss] )
57
- fragments.should be_empty
58
- end
59
-
60
- end
61
-
62
- describe ':peroxy_to_carboxy' do
63
- it 'works' do
64
- mol = Rubabel["NCCC(OO)CC"]
65
- frag_sets = mol.fragment( rules: [:peroxy_to_carboxy] )
66
- frag_sets.size.should == 2
67
- frag_sets.flatten(1).map(&:csmiles).sort.should == ["CC", "CCC(=O)O", "CC[NH3+]", "OC(=O)CC[NH3+]"]
68
- end
69
- end
70
-
71
- describe ':sp3c_oxygen_asymmetric_far_sp3', :pending do
72
- it 'splits like sp3c_oxygen_double_bond except oxygen takes the electrons' do
73
- $VERBOSE = 3
74
- mol = Rubabel["NCCCOCC"]
75
- frag_sets = mol.fragment( rules: [:sp3c_oxygen_asymmetric_far_sp3] )
76
- $VERBOSE = nil
77
- frag_sets.size.should == 2
7
+ #describe Rubabel::Molecule::Fragmentable do
8
+
9
+ ## :peroxy_to_carboxy
10
+ ## :oxygen_asymmetric_sp3, :nitrogen_asymmetric_sp3,
11
+ ## :internal_phosphoester
12
+
13
+ #describe 'fragmentation rules' do
14
+ ## coenzyme: CC1=CC(=O)C=CC1=O
15
+ ## 2-methylcyclohexa-2,5-diene-1,4-dione
16
+
17
+ #let(:test_mol) { "COP(=O)(O)OCNCOCC(OO)C(=O)O" }
18
+
19
+ #it 'raises an error for a bad rule' do
20
+ #mol = Rubabel["CCNC"]
21
+ #expect { mol.fragment(rules: [:wackiness]) }.to raise_error
22
+ #end
23
+
24
+ #describe 'cad_o: carbonyl appendage dump ' do
25
+ ## a primary oxygen or peroxide => C=O appendage dump
26
+
27
+ #describe 'cad_o: primary alcohol' do
28
+ #mol = Rubabel["NCC(O)CC"]
29
+ #frags = mol.fragment(rules: [:cad_o])
30
+ #frags.flatten(1).map(&:csmiles).should == ["C[NH3+]", "CCC=O", "C([NH3+])C=O", "CC"]
31
+ #end
32
+
33
+ #describe 'peroxide' do
34
+ #mol = Rubabel["NCC(OO)CC"]
35
+ #frags = mol.fragment(rules: [:cad_oo])
36
+ #frags.flatten(1).each_with_index do |f,i|
37
+ #f.write("mol#{i}.svg")
38
+ #end
39
+ #frags.flatten(1).map(&:csmiles).should == ["OC[NH3+]", "CCC=O", "C([NH3+])C=O", "CCO"]
40
+ #end
41
+
42
+ #describe 'cad_o: carboxylate' do
43
+ #mol = Rubabel["CCC(=O)O"]
44
+ #pieces = mol.fragment(rules: [:cad_o])
45
+ #pieces.flatten(1).map(&:csmiles).should == ["[CH2-]C", "O=C=O"]
46
+ #end
47
+
48
+ #describe 'cad_o: carboxylic acid' do
49
+ #mol = Rubabel["CCC(=O)O"]
50
+ #mol.add_h!(1.5)
51
+ #pieces = mol.fragment(rules: [:cad_o])
52
+ #pieces.flatten(1).map(&:csmiles).should == ["CC", "O=C=O"]
53
+ #end
54
+ #end
55
+
56
+ #describe 'oxe: oxygen electron stealing' do
57
+ ## oxygen just steals the electron pair it is attached to. This
58
+ ## typically results in a negatively charged oxygen and a positively
59
+ ## charged carbo-cation.
60
+ #describe 'ether to ions' do
78
61
  #mol = Rubabel["NCCOCC"]
79
- #p mol.fragment( rules: [:sp3c_oxygen_asymmetric_far_sp3] )
80
- #mol = Rubabel["NCOC"]
81
- #p mol.fragment( rules: [:sp3c_oxygen_asymmetric_far_sp3] )
82
- end
83
- end
84
-
85
- describe ':sp3c_oxygen_double_bond_water_loss' do
86
-
87
- it 'does h2o loss of alcohol' do
88
- mol = Rubabel["NCCC(O)CC"]
89
- fragments = mol.fragment( rules: [:sp3c_oxygen_double_bond_water_loss] )
90
- fragments.flatten(1).map(&:csmiles).sort.should == ["CC=CCC[NH3+]", "CCC=CC[NH3+]", "O", "O"]
91
- end
92
-
93
- it 'h2o loss does not allow bad chemistry' do
94
- # lone pair and double bond resonance ?
95
- mol = Rubabel["NCC(O)CC"]
96
- fragments = mol.fragment( rules: [:sp3c_oxygen_double_bond_water_loss] )
97
- fragments.flatten(1).map(&:csmiles).sort.should == ["CC=CC[NH3+]", "O"]
98
-
99
- mol = Rubabel["NC(O)CC"]
100
- fragments = mol.fragment( rules: [:sp3c_oxygen_double_bond_water_loss] )
101
- fragments.flatten(1).map(&:csmiles).sort.should == []
102
- end
103
- end
104
-
105
- describe 'sp3c_oxygen_double_bond_far_side_sp2' do
106
-
107
- it 'does not cleave esters without sp3 carbons available for double bond' do
108
- mol = Rubabel["NCCC(=O)OC"]
109
- pieces = mol.fragment( rules: [:sp3c_oxygen_double_bond_far_side_sp2] )
110
- pieces.should be_empty
111
- end
112
-
113
- it 'cleaves esters on far side of singly bonded oxygen' do
114
- mol = Rubabel["NCCC(=O)OCC"]
115
- pieces = mol.fragment( rules: [:sp3c_oxygen_double_bond_far_side_sp2] )
116
- pieces.size.should == 1 # one set
117
- the_pair = pieces.first
118
- csmiles = the_pair.map(&:csmiles)
119
- csmiles.should include("OC(=O)CC[NH3+]")
120
- csmiles.should include("C=C")
121
- end
122
-
123
- end
124
-
125
- describe ':alcohol_to_aldehyde' do
126
- it 'cleaves beside alcohols to generate an aldehyde' do
127
- mol = Rubabel["NCCC(O)CC"]
128
- mol.correct_for_ph!
129
- total_mass = mol.add_h!.mass
130
-
131
- pieces = mol.fragment(rules: [:alcohol_to_aldehyde])
132
- pieces.size.should == 2
133
- pieces.map(&:size).should == [2,2]
134
- pieces.flatten(1).map(&:csmiles).should == ["CC[NH3+]", "CCC=O", "C(C=O)C[NH3+]", "CC"]
135
- pieces.each do |pair|
136
- pair.map(&:mass).reduce(:+).should == total_mass
137
- end
138
- end
139
- end
140
-
141
- end
142
- end
62
+ ##mol.add_h!
63
+ #pieces = mol.fragment(rules: [:oxed_ether])
64
+ #m = pieces.first.first
65
+ ##[CH2+]CH2NH3+
66
+ ## C2H7N
67
+
68
+ #p m
69
+ #m.add_h!
70
+ #h = m.atoms[4].atoms.find {|a| a.el == :h }
71
+ #m.delete_atom(h)
72
+ #p m
73
+ #p m.atoms
74
+ ##mol = Rubabel["NCO"]
75
+ ##pieces = mol.fragment(rules: [:oxed_ether])
76
+ ##pieces.size.should == 0
77
+ #end
78
+
79
+ #describe 'ester to ions' do
80
+ #end
81
+
82
+ #describe 'carboxyl group' do
83
+ #end
84
+
85
+ #describe 'phosphodiester' do
86
+ #end
87
+ #end
88
+
89
+ ## this is really a subset of oxygen bond stealing: if the negatively
90
+ ## charged oxygen can rip off a nearby proton, it will.
91
+ #describe 'oxygen alpha/beta/gamma hydrogen stealing' do
92
+ #describe 'primary alcohol giving water loss' do
93
+ #end
94
+
95
+ #describe 'peroxide carbonyl formation' do
96
+ #end
97
+
98
+ #describe 'ether to alcohol' do
99
+ #end
100
+
101
+ #describe 'ester to alcohol' do
102
+ #end
103
+
104
+ #describe 'phosphodiester' do
105
+ #end
106
+ #end
107
+
108
+ #end
109
+ #end
110
+
111
+
112
+
113
+
114
+ ##describe ':sp3c_nitrogen_double_bond' do
115
+
116
+ ##it 'cleaves like an ether a secondary NH group if possible' do
117
+ ##mol = Rubabel["CCNC"]
118
+ ##frag_sets = mol.fragment(rules: [:sp3c_nitrogen_double_bond])
119
+ ##frag_sets.size.should == 1
120
+ ##csmiles = frag_sets.first.map(&:csmiles)
121
+ ##csmiles.should include("C=C")
122
+ ##csmiles.should include("C[NH3+]")
123
+ ##end
124
+
125
+ ##it 'will not cleave if not possible' do
126
+ ##mol = Rubabel["CNC"]
127
+ ##frag_sets = mol.fragment(rules: [:sp3c_nitrogen_double_bond])
128
+ ##frag_sets.should be_empty
129
+ ##end
130
+
131
+ ##end
132
+
133
+ ##describe ':co2_loss' do
134
+ ##it 'loss of CO2 from carboxy group with charge transfer' do
135
+ ##mol = Rubabel["NCC(=O)O"]
136
+ ##frag_sets = mol.fragment( rules: [:co2_loss] )
137
+ ##frag_sets.size.should == 1
138
+ ##csmiles = frag_sets.first.map(&:csmiles)
139
+
140
+ ##csmiles.should include("[CH2-][NH3+]")
141
+ ##csmiles.should include("O=C=O")
142
+ ##end
143
+
144
+ ##it "doesn't remove CO2 if adjacent is not c3" do
145
+ ##mol = Rubabel["C=CC(=O)O"]
146
+ ##fragments = mol.fragment( rules: [:co2_loss] )
147
+ ##fragments.should be_empty
148
+ ##end
149
+
150
+ ##end
151
+
152
+ ##describe ':peroxy_to_carboxy' do
153
+ ##it 'works' do
154
+ ##mol = Rubabel["NCCC(OO)CC"]
155
+ ##frag_sets = mol.fragment( rules: [:peroxy_to_carboxy] )
156
+ ##frag_sets.size.should == 2
157
+ ##frag_sets.flatten(1).map(&:csmiles).sort.should == ["CC", "CCC(=O)O", "CC[NH3+]", "OC(=O)CC[NH3+]"]
158
+ ##end
159
+ ##end
160
+
161
+ ##describe ':sp3c_oxygen_asymmetric_far_sp3', :pending do
162
+ ##it 'splits like sp3c_oxygen_double_bond except oxygen takes the electrons' do
163
+ ##$VERBOSE = 3
164
+ ##mol = Rubabel["NCCCOCC"]
165
+ ##frag_sets = mol.fragment( rules: [:sp3c_oxygen_asymmetric_far_sp3] )
166
+ ##$VERBOSE = nil
167
+ ##frag_sets.size.should == 2
168
+ ###mol = Rubabel["NCCOCC"]
169
+ ###p mol.fragment( rules: [:sp3c_oxygen_asymmetric_far_sp3] )
170
+ ###mol = Rubabel["NCOC"]
171
+ ###p mol.fragment( rules: [:sp3c_oxygen_asymmetric_far_sp3] )
172
+ ##end
173
+ ##end
174
+
175
+ ##describe ':sp3c_oxygen_double_bond_water_loss' do
176
+
177
+ ##it 'does h2o loss of alcohol' do
178
+ ##mol = Rubabel["NCCC(O)CC"]
179
+ ##fragments = mol.fragment( rules: [:sp3c_oxygen_double_bond_water_loss] )
180
+ ##fragments.flatten(1).map(&:csmiles).sort.should == ["CC=CCC[NH3+]", "CCC=CC[NH3+]", "O", "O"]
181
+ ##end
182
+
183
+ ##it 'h2o loss does not allow bad chemistry' do
184
+ ### lone pair and double bond resonance ?
185
+ ##mol = Rubabel["NCC(O)CC"]
186
+ ##fragments = mol.fragment( rules: [:sp3c_oxygen_double_bond_water_loss] )
187
+ ##fragments.flatten(1).map(&:csmiles).sort.should == ["CC=CC[NH3+]", "O"]
188
+
189
+ ##mol = Rubabel["NC(O)CC"]
190
+ ##fragments = mol.fragment( rules: [:sp3c_oxygen_double_bond_water_loss] )
191
+ ##fragments.flatten(1).map(&:csmiles).sort.should == []
192
+ ##end
193
+ ##end
194
+
195
+ ##describe 'sp3c_oxygen_double_bond_far_side_sp2' do
196
+
197
+ ##it 'does not cleave esters without sp3 carbons available for double bond' do
198
+ ##mol = Rubabel["NCCC(=O)OC"]
199
+ ##pieces = mol.fragment( rules: [:sp3c_oxygen_double_bond_far_side_sp2] )
200
+ ##pieces.should be_empty
201
+ ##end
202
+
203
+ ##it 'cleaves esters on far side of singly bonded oxygen' do
204
+ ##mol = Rubabel["NCCC(=O)OCC"]
205
+ ##pieces = mol.fragment( rules: [:sp3c_oxygen_double_bond_far_side_sp2] )
206
+ ##pieces.size.should == 1 # one set
207
+ ##the_pair = pieces.first
208
+ ##csmiles = the_pair.map(&:csmiles)
209
+ ##csmiles.should include("OC(=O)CC[NH3+]")
210
+ ##csmiles.should include("C=C")
211
+ ##end
212
+
213
+ ##end
214
+
215
+ ##describe ':alcohol_to_aldehyde' do
216
+ ##it 'cleaves beside alcohols to generate an aldehyde' do
217
+ ##mol = Rubabel["NCCC(O)CC"]
218
+ ##mol.correct_for_ph!
219
+ ##total_mass = mol.add_h!.mass
220
+
221
+ ##pieces = mol.fragment(rules: [:alcohol_to_aldehyde])
222
+ ##pieces.size.should == 2
223
+ ##pieces.map(&:size).should == [2,2]
224
+ ##pieces.flatten(1).map(&:csmiles).should == ["CC[NH3+]", "CCC=O", "C(C=O)C[NH3+]", "CC"]
225
+ ##pieces.each do |pair|
226
+ ##pair.map(&:mass).reduce(:+).should == total_mass
227
+ ##end
228
+ ##end
229
+ ##end
@@ -10,6 +10,13 @@ describe Rubabel::Molecule do
10
10
  end
11
11
  end
12
12
 
13
+ #xit 'can add a hydrogen to the formula' do
14
+ #mol = Rubabel["CCC"]
15
+ #p mol.formula
16
+ #mol.add_hydrogen_to_formula!
17
+ #p mol.formula
18
+ #end
19
+
13
20
  describe 'png output' do
14
21
  it 'creates a png image (corresponds to the svg)' do
15
22
  mol = Rubabel["NCC(=O)O"]
@@ -39,6 +46,24 @@ describe Rubabel::Molecule do
39
46
  end
40
47
  end
41
48
 
49
+ specify 'eql? and equal? mean the objects modify the same underlying openbabel molecule data' do
50
+ mol = Rubabel["C"]
51
+ eq_mol = mol.atoms.first.mol
52
+ mol.equal?(eq_mol).should be_true
53
+ another = Rubabel["C"]
54
+ mol.equal?(another).should be_false
55
+ end
56
+
57
+ specify '== means the canonical smiles strings (:csmiles) are equal' do
58
+ mol1 = Rubabel["CCO"]
59
+ mol2 = Rubabel["OCC"]
60
+ (mol1 == mol2).should be_true
61
+ mol2.atoms[0].charge += 1
62
+ (mol1 == mol2).should be_false
63
+ mol3 = Rubabel["CCCO"]
64
+ (mol1 == mol3).should be_false
65
+ end
66
+
42
67
  specify '#add_atom! adds an atom given an atomic number and returns it' do
43
68
  mol = Rubabel["CCO"]
44
69
  before_size = mol.atoms.size
@@ -48,29 +73,30 @@ describe Rubabel::Molecule do
48
73
  mol.csmiles.should == "CCO.C"
49
74
  end
50
75
 
76
+ specify '#dup duplicates the molecule' do
77
+ mol = Rubabel["CCO"]
78
+ dup_mol = mol.dup
79
+ mol.atoms[0].ob.set_atomic_num(9)
80
+ mol.csmiles.should == "OCF"
81
+ dup_mol.csmiles.should == "CCO"
82
+ end
83
+
51
84
  specify '#add_atom! with a Rubabel::Atom'
52
85
 
53
- specify '#add_bond! adds a bond (and updates atoms)' do
54
- mol = Rubabel["CCO"]
55
- atom = mol.add_atom!(0)
56
- mol.add_bond!(mol.atoms[1], atom)
57
- mol.csmiles.should == '*C(O)C'
86
+ describe '#add_bond! adds a bond (and updates atoms)' do
87
+ specify 'given two atoms' do
88
+ mol = Rubabel["CCO"]
89
+ atom = mol.add_atom!(0)
90
+ mol.add_bond!(mol.atoms[1], atom)
91
+ mol.csmiles.should == '*C(O)C'
92
+ end
58
93
  end
59
94
 
60
- # specify '#add_bond adds a bond (and updates atoms)' do
61
- # c = Rubabel::Atom[:c]
62
- # o = Rubabel::Atom[:o]
63
- # mol = Rubabel::Molecule.from_atoms_and_bonds([c,o])
64
- # mol.add_bond(c,o)
65
- # puts "BONDS:"
66
- # p mol.bonds
67
- # puts "ATOMS:"
68
- # p mol.atoms
69
- # puts "C ATOMS:"
70
- # p c.atoms
71
- # puts "O ATOMS:"
72
- # p o.atoms
73
- # end
95
+ specify '#atom(id) retrieves atom by id num' do
96
+ mol = Rubabel["CCO"]
97
+ o = mol.find {|a| a.el == :o }
98
+ mol.atom(o.id).id.should == o.id
99
+ end
74
100
 
75
101
  specify '#swap! can swap atoms around' do
76
102
  mol = Rubabel["NCC(=O)O"]
@@ -117,8 +143,20 @@ describe Rubabel::Molecule do
117
143
  ar.first.should be_a(OpenBabel::OBRing)
118
144
  end
119
145
 
146
+ describe 'making carbo-cations: spin_multiplicity and charges' do
147
+ # http://openbabel.org/docs/2.3.1/Features/Radicals.html
148
+ subject { mol = Rubabel["CC"] }
149
+ it 'can be turned into a carbocation' do
150
+ mol = subject
151
+ c = mol.atoms[0]
152
+ c.ob.set_spin_multiplicity 2
153
+ c.charge += 1
154
+ mol.csmiles.should == "C[CH2+]"
155
+ end
156
+ end
157
+
120
158
  describe 'masses' do
121
- subject { Rubabel::Molecule.from_string("C(=O)COC(=O)C[NH3+]") }
159
+ subject { Rubabel["C(=O)COC(=O)C[NH3+]"] }
122
160
  it '#mol_wt (or #avg_mass)' do
123
161
  subject.mol_wt.should be_within(0.000001).of(118.11121999999999)
124
162
  end
@@ -138,7 +176,7 @@ describe Rubabel::Molecule do
138
176
 
139
177
  describe 'pH' do
140
178
 
141
- subject { Rubabel::Molecule.from_string("NCC(=O)OCC(=O)O") }
179
+ subject { Rubabel["NCC(=O)OCC(=O)O"] }
142
180
 
143
181
  it '#correct_for_ph! neutral' do
144
182
  subject.correct_for_ph!.to_s.should == '[O-]C(=O)COC(=O)C[NH3+]'
@@ -220,6 +258,7 @@ describe Rubabel::Molecule do
220
258
  describe 'breaking a molecule' do
221
259
  before(:each) do
222
260
  @mol = Rubabel::Molecule.from_string("NC(=O)CO")
261
+ @n = @mol.find {|a| a.el == :n }
223
262
  end
224
263
 
225
264
  it 'num_atoms, atoms and each_atom are sensitive to #add_h!' do
@@ -256,6 +295,20 @@ describe Rubabel::Molecule do
256
295
  csmiles.sort.should == %w(N CC=O O).sort
257
296
  end
258
297
 
298
+ it 'can split fragments (akin to separate)' do
299
+ @mol.delete_bond(@n, @n.atoms.first)
300
+ pieces = @mol.split
301
+ pieces.map(&:csmiles).sort.should == ["N", "OCC=O"]
302
+ end
303
+
304
+ it 'can iterate through fragments' do
305
+ expected = %w(N OCC=O)
306
+ @mol.delete_bond(@n, @n.atoms.first)
307
+ @mol.each_fragment do |frag|
308
+ frag.csmiles.should == expected.shift
309
+ end
310
+ end
311
+
259
312
  end
260
313
 
261
314
  describe 'matching patterns (SMARTS)' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubabel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-06 00:00:00.000000000 Z
12
+ date: 2012-10-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: openbabel