Class: Rubabel::Molecule

Inherits:
Object show all
Includes:
Enumerable, Fragmentable
Defined in:
lib/rubabel/molecule/fragmentable.rb,
lib/rubabel/molecule.rb

Defined Under Namespace

Modules: Fragmentable

Constant Summary collapse

DEFAULT_FINGERPRINT =
"FP2"
DEFAULT_OUT_TYPE =
:can
DEFAULT_IN_TYPE =
:smi

Constants included from Fragmentable

Fragmentable::DEFAULT_OPTIONS, Fragmentable::RULES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Enumerable

#index_by, #uniq_by

Methods included from Fragmentable

#alcohol_to_aldehyde, #allowable_fragment_sets!, #allowable_fragmentation?, #carbon_oxygen_esteal, #carbonyl_oxygen_dump, #co2_loss, #electrophile_snatches_electrons, #feint_double_bond, #feint_e_transfer, #fragment, #near_side_double_bond_break, #peroxy_to_carboxy

Constructor Details

#initialize(obmol) ⇒ Molecule

Returns a new instance of Molecule.



148
149
150
# File 'lib/rubabel/molecule.rb', line 148

def initialize(obmol)
  @ob = obmol
end

Instance Attribute Details

#obObject

the OpenBabel::OBmol object



82
83
84
# File 'lib/rubabel/molecule.rb', line 82

def ob
  @ob
end

Class Method Details

.from_atoms_and_bonds(atoms = [], bonds = []) ⇒ Object



103
104
105
106
107
108
# File 'lib/rubabel/molecule.rb', line 103

def from_atoms_and_bonds(atoms=[], bonds=[])
  obj = self.new( OpenBabel::OBMol.new )
  atoms.each {|atom| obj.add_atom(atom) }
  bonds.each {|bond| obj.add_bond(bond) }
  obj
end

.from_file(file, type = nil) ⇒ Object



90
91
92
93
# File 'lib/rubabel/molecule.rb', line 90

def from_file(file, type=nil)
  (obmol, obconv, not_at_end) = Rubabel.read_first_obmol(file, type).first
  Rubabel::Molecule.new(obmol)
end

.from_string(string, type = DEFAULT_IN_TYPE) ⇒ Object



95
96
97
98
99
100
101
# File 'lib/rubabel/molecule.rb', line 95

def from_string(string, type=DEFAULT_IN_TYPE)
  obmol = OpenBabel::OBMol.new
  obconv = OpenBabel::OBConversion.new
  obconv.set_in_format(type.to_s) || raise(ArgumentError, "invalid format #{type}")
  obconv.read_string(obmol, string) || raise(ArgumentError, "invalid string" )
  self.new(obmol)
end

.tanimoto(mol1, mol2, type = DEFAULT_FINGERPRINT) ⇒ Object



86
87
88
# File 'lib/rubabel/molecule.rb', line 86

def tanimoto(mol1, mol2, type=DEFAULT_FINGERPRINT)
  OpenBabel::OBFingerprint.tanimoto(mol1.ob_fingerprint(type), mol2.ob_fingerprint(type))
end

Instance Method Details

#==(other) ⇒ Object

defined as whether the csmiles strings are identical. This incorporates more information than the FP2 fingerprint, for instance (try changing the charge and see how it does not influence the fingerprint). Obviously, things like title or data will not be evaluated with ==. See equal? if you are looking for identity. More stringent comparisons will have to be done by hand!



297
298
299
# File 'lib/rubabel/molecule.rb', line 297

def ==(other)
  other.respond_to?(:csmiles) && (csmiles == other.csmiles)
end

#add_atom!(atomic_num = 1) ⇒ Object

arg is an atomic number. returns the newly created atom



112
113
114
115
116
117
118
119
# File 'lib/rubabel/molecule.rb', line 112

def add_atom!(atomic_num=1)
  # jtp implementation:
  # @ob.add_atom(atom.ob)
  new_obatom = @ob.new_atom
  new_obatom.set_atomic_num(atomic_num)
  #@ob.add_atom(new_obatom)
  Rubabel::Atom.new(new_obatom)
end

#add_bond!(atom1, atom2, order = 1) ⇒ Object

takes a pair of Rubabel::Atom objects and adds a bond to the molecule returns whether the bond creation was successful.



423
424
425
# File 'lib/rubabel/molecule.rb', line 423

def add_bond!(atom1, atom2, order=1)
  @ob.add_bond(atom1.idx, atom2.idx, order)
end

#add_h!(ph = nil, polaronly = false) ⇒ Object

returns self. Corrects for ph if ph is not nil. NOTE: the reversal of arguments from the OpenBabel api.



203
204
205
206
207
208
209
210
# File 'lib/rubabel/molecule.rb', line 203

def add_h!(ph=nil, polaronly=false)
  if ph.nil?
    @ob.add_hydrogens(polaronly)
  else
    @ob.add_hydrogens(polaronly, true, ph)
  end
  self
end

#add_hydrogen_to_formula!Object

adds 1 hydrogen to the formula and returns self



641
642
643
644
645
646
647
648
649
650
651
652
653
# File 'lib/rubabel/molecule.rb', line 641

def add_hydrogen_to_formula!
  string = @ob.get_formula
  substituted = false
  new_string = string.sub(/H(\d*)/) { substituted=true; "H#{$1.to_i+1}" }
  unless substituted
    new_string = string.sub("^(C?\d*)") { $1 + 'H' }
  end
  puts 'HERE'
  p string
  p new_string
  #@ob.set_formula(new_string)
  self
end

#add_polar_h!Object

only adds polar hydrogens. returns self



213
214
215
216
# File 'lib/rubabel/molecule.rb', line 213

def add_polar_h!
  @ob.add_polar_hydrogens
  self
end

#atom(id) ⇒ Object

gets the atom by id



345
346
347
# File 'lib/rubabel/molecule.rb', line 345

def atom(id)
  @ob.get_atom_by_id(id).upcast
end

#atomsObject

returns the array of atoms. Consider using #each



350
351
352
# File 'lib/rubabel/molecule.rb', line 350

def atoms
  each_atom.map.to_a
end

#bond(id) ⇒ Object

gets the bond by id



335
336
337
# File 'lib/rubabel/molecule.rb', line 335

def bond(id)
  @ob.get_bond_by_id(id).upcast
end

#bondsObject

returns the array of bonds. Consider using #each_bond



340
341
342
# File 'lib/rubabel/molecule.rb', line 340

def bonds
  each_bond.map.to_a
end

#center!Object

centers the molecule (deals with the atomic coordinate systems for 2D or 3D molecules). returns self.



591
592
593
594
# File 'lib/rubabel/molecule.rb', line 591

def center!
  @ob.center
  self
end

#chargeObject



129
# File 'lib/rubabel/molecule.rb', line 129

def charge() @ob.get_total_charge end

#charge=(v) ⇒ Object



130
# File 'lib/rubabel/molecule.rb', line 130

def charge=(v) @ob.set_total_charge(v) end

#convert_dative_bonds!Object

returns self



584
585
586
587
# File 'lib/rubabel/molecule.rb', line 584

def convert_dative_bonds!
  @ob.convert_dative_bonds
  self
end

#correct_for_ph!(ph = 7.4) ⇒ Object

returns self. If ph is nil, then #neutral! is called



219
220
221
222
# File 'lib/rubabel/molecule.rb', line 219

def correct_for_ph!(ph=7.4)
  ph.nil? ? neutral! : @ob.correct_for_ph(ph)
  self
end

#csmilesObject

returns just the smiles string (not the id)



263
264
265
# File 'lib/rubabel/molecule.rb', line 263

def csmiles
  to_s(:can)
end

#dataObject

returns a Rubabel::MoleculeData hash



475
476
477
# File 'lib/rubabel/molecule.rb', line 475

def data
  Rubabel::MoleculeData.new(@ob)
end

#delete(obj) ⇒ Object

obj is an atom or bond



383
384
385
386
387
388
389
390
391
392
# File 'lib/rubabel/molecule.rb', line 383

def delete(obj)
  case obj
  when Rubabel::Bond
    delete_bond(obj)
  when Rubabel::Atom
    delete_atom(obj)
  else 
    raise(ArgumentError, "don't know how to delete objects of type: #{obj.class}")
  end
end

#delete_and_restore_bonds(*bonds, &block) ⇒ Object

yields self after deleting the specified bonds. When the block is closed the bonds are restored. Returns whatever is returned from the block.



430
431
432
433
434
435
436
437
438
439
# File 'lib/rubabel/molecule.rb', line 430

def delete_and_restore_bonds(*bonds, &block)
  bonds.each do |bond|
    unless @ob.delete_bond(bond.ob, false)
      raise "#{bond.inspect} not deleted!" 
    end
  end
  reply = block.call(self)
  bonds.each {|bond| @ob.add_bond(bond.ob) }
  reply
end

#delete_atom(atom) ⇒ Object



121
122
123
# File 'lib/rubabel/molecule.rb', line 121

def delete_atom(atom)
  @ob.delete_atom(atom.ob, false)
end

#delete_bond(*args) ⇒ Object

if given a bond, deletes it (doesn’t garbage collect). If given two atoms, deletes the bond between them.



396
397
398
399
400
401
402
403
# File 'lib/rubabel/molecule.rb', line 396

def delete_bond(*args)
  case args.size
  when 1
    @ob.delete_bond(args[0].ob, false)
  when 2
    @ob.delete_bond(args[0].get_bond(args[1]).ob, false)
  end
end

#dimObject



354
355
356
# File 'lib/rubabel/molecule.rb', line 354

def dim
  @ob.get_dimension
end

#each_atom(&block) ⇒ Object Also known as: each

iterates over the molecule’s Rubabel::Atom objects



302
303
304
305
306
307
308
309
310
311
# File 'lib/rubabel/molecule.rb', line 302

def each_atom(&block)
  # could use the C++ iterator in the future
  block or return enum_for(__method__)
  iter = @ob.begin_atoms
  atom = @ob.begin_atom(iter)
  while atom
    block.call atom.upcast
    atom = @ob.next_atom(iter)
  end
end

#each_bond(&block) ⇒ Object

iterates over the molecule’s Rubabel::Bond objects



315
316
317
318
319
320
321
322
323
324
325
# File 'lib/rubabel/molecule.rb', line 315

def each_bond(&block)
  # could use the C++ iterator in the future
  block or return enum_for(__method__)
  iter = @ob.begin_bonds
  obbond = @ob.begin_bond(iter)
  while obbond
    block.call obbond.upcast
    obbond = @ob.next_bond(iter)
  end
  self
end

#each_fragment(&block) ⇒ Object



454
455
456
457
458
459
# File 'lib/rubabel/molecule.rb', line 454

def each_fragment(&block)
  block or return enum_for(__method__)
  @ob.separate.each do |ob_mol|
    block.call( ob_mol.upcast )
  end
end

#each_match(smarts_or_string, uniq = true, &block) ⇒ Object

yields atom arrays matching the pattern. returns an enumerator if no block is given



166
167
168
169
170
171
172
# File 'lib/rubabel/molecule.rb', line 166

def each_match(smarts_or_string, uniq=true, &block)
  block or return enum_for(__method__, smarts_or_string, uniq)
  _atoms = self.atoms
  smarts_indices(smarts_or_string, uniq).each do |ar|
    block.call(_atoms.values_at(*ar))
  end
end

#equal?(other) ⇒ Boolean Also known as: eql?

checks to see if the molecules are the same OBMol object underneath by modifying one and seeing if the other changes. This is because openbabel routinely creates new objects that point to the same underlying data store, so even checking for OBMol equivalency is not enough.

Returns:

  • (Boolean)


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/rubabel/molecule.rb', line 272

def equal?(other)
  return false unless other.is_a?(self.class)
  are_identical = false
  if self.title == other.title
    begin
      obj_id = self.object_id.to_s
      self.title += obj_id
      are_identical = (self.title == other.title)
    ensure
      self.title.sub(/#{obj_id}$/,'')
    end
    are_identical
  else
    false
  end
end

#exact_massObject



137
# File 'lib/rubabel/molecule.rb', line 137

def exact_mass() @ob.get_exact_mass end

#formulaObject

returns a string representation of the molecular formula. Not sensitive to add_h!



146
# File 'lib/rubabel/molecule.rb', line 146

def formula() @ob.get_formula end

#graph_diameterObject

obconv.add_option(“u”,OpenBabel::OBConversion::OUTOPTIONS)

self

end



629
630
631
632
633
634
635
636
637
638
# File 'lib/rubabel/molecule.rb', line 629

def graph_diameter
  distance_matrix = Array.new
  self.atoms.each do |a|
    iter = OpenBabel::OBMolAtomBFSIter.new(self.ob, a.idx)
    while iter.inc.deref do
      distance_matrix << iter.current_depth - 1
    end
  end
  distance_matrix.max
end

#highlight_substructure!(substructure, color = 'red') ⇒ Object

returns self



609
610
611
612
613
614
# File 'lib/rubabel/molecule.rb', line 609

def highlight_substructure!(substructure, color='red')
  tmpconv = OpenBabel::OBConversion.new
  tmpconv.add_option("s",OpenBabel::OBConversion::GENOPTIONS, "#{substructure} #{color}")
  self.ob.do_transformations(tmpconv.get_options(OpenBabel::OBConversion::GENOPTIONS), tmpconv)
  self
end

#hydrogens_added?Boolean Also known as: h_added?

are there hydrogens added yet

Returns:

  • (Boolean)


196
197
198
# File 'lib/rubabel/molecule.rb', line 196

def hydrogens_added?
  @ob.has_hydrogens_added
end

#initialize_copy(source) ⇒ Object

creates a deep copy of the molecule (even the atoms are duplicated)



328
329
330
331
332
# File 'lib/rubabel/molecule.rb', line 328

def initialize_copy(source)
  super
  @ob = OpenBabel::OBMol.new(source.ob)
  self
end

#inspectObject



579
580
581
# File 'lib/rubabel/molecule.rb', line 579

def inspect
  "#<Mol #{to_s}>"
end

#kekulize!Object

returns self



597
598
599
600
# File 'lib/rubabel/molecule.rb', line 597

def kekulize!
  @ob.kekulize
  self
end

#local_optimize!(forcefield = DEFAULT_FORCEFIELD, steps = 500) ⇒ Object

adds hydrogens if necessary. Performs only steepest descent optimization (no rotors optimized) returns self



500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/rubabel/molecule.rb', line 500

def local_optimize!(forcefield=DEFAULT_FORCEFIELD, steps=500)
  add_h! unless hydrogens_added?
  if dim == 3
    ff = Rubabel.force_field(forcefield.to_s)
    ff.setup(@ob) || raise(OpenBabelUnableToSetupForceFieldError)
    ff.steepest_descent(steps)  # is the default termination count 1.0e-4 (used in obgen?)
    ff.update_coordinates(@ob)
  else
    make_3d!(forcefield, steps) 
  end
  self
end

#make_3d!(forcefield = DEFAULT_FORCEFIELD, steps = 50) ⇒ Object Also known as: make3d!

does a bit of basic local optimization unless steps is set to nil returns self



522
523
524
525
526
527
# File 'lib/rubabel/molecule.rb', line 522

def make_3d!(forcefield=DEFAULT_FORCEFIELD, steps=50)
  BUILDER.build(@ob)
  @ob.add_hydrogens(false, true) unless hydrogens_added?
  local_optimize!(forcefield, steps) if steps
  self
end

#massObject

returns the exact_mass corrected for charge gain/loss



140
141
142
# File 'lib/rubabel/molecule.rb', line 140

def mass
  @ob.get_exact_mass - (@ob.get_total_charge * Rubabel::MASS_E)
end

#matches(smarts_or_string, uniq = true) ⇒ Object

returns an array of matching atom sets. Consider using each_match.



175
176
177
# File 'lib/rubabel/molecule.rb', line 175

def matches(smarts_or_string, uniq=true)
  each_match(smarts_or_string, uniq).map.to_a
end

#matches?(smarts_or_string) ⇒ Boolean

Returns:

  • (Boolean)


179
180
181
182
# File 'lib/rubabel/molecule.rb', line 179

def matches?(smarts_or_string)
  # TODO: probably a more efficient way to do this using API
  smarts_indices(smarts_or_string).size > 0
end

#mol_wtObject Also known as: avg_mass



134
# File 'lib/rubabel/molecule.rb', line 134

def mol_wt() @ob.get_mol_wt end

#neutral!Object

simple method to coerce the molecule into a neutral charge state. It does this by removing any charge from each atom and then removing the hydrogens (which will then can be added back by the user and will be added back with proper valence). If the molecule had hydrogens added it will return the molecule with hydrogens added returns self.



230
231
232
233
234
235
236
# File 'lib/rubabel/molecule.rb', line 230

def neutral!
  had_hydrogens = h_added?
  atoms.each {|atom| atom.charge = 0 if (atom.charge != 0) }
  remove_h!
  add_h! if had_hydrogens 
  self
end

#new_bondObject

creates a new (as yet unspecified) bond associated with the molecule and gives it a unique id



417
418
419
# File 'lib/rubabel/molecule.rb', line 417

def new_bond
  @ob.new_bond.upcast
end

#num_atomsObject

sensitive to add_h!



480
# File 'lib/rubabel/molecule.rb', line 480

def num_atoms() @ob.num_atoms  end

#num_bondsObject



481
# File 'lib/rubabel/molecule.rb', line 481

def num_bonds() @ob.num_bonds  end

#num_hvy_atomsObject



482
# File 'lib/rubabel/molecule.rb', line 482

def num_hvy_atoms() @ob.num_hvy_atoms  end

#num_residuesObject



483
# File 'lib/rubabel/molecule.rb', line 483

def num_residues() @ob.num_residues  end

#num_rotorsObject



484
# File 'lib/rubabel/molecule.rb', line 484

def num_rotors() @ob.num_rotors  end

#ob_fingerprint(type = DEFAULT_FINGERPRINT) ⇒ Object

returns a std::vector<unsigned int> that can be passed directly into the OBFingerprint.tanimoto method



375
376
377
378
379
380
# File 'lib/rubabel/molecule.rb', line 375

def ob_fingerprint(type=DEFAULT_FINGERPRINT)
  fprinter = OpenBabel::OBFingerprint.find_fingerprint(type) || raise(ArgumentError, "fingerprint type not found")
  fp = OpenBabel::VectorUnsignedInt.new
  fprinter.get_fingerprint(@ob, fp) || raise("failed to get fingerprint for #{mol}")
  fp
end

#ob_sssrObject

returns an array of OpenBabel::OBRing objects.



185
186
187
# File 'lib/rubabel/molecule.rb', line 185

def ob_sssr
  @ob.get_sssr.to_a
end

#png_transformer(type_s, out_options = {}, &block) ⇒ Object

yields the type of object. Expects the block to yield the image string.



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/rubabel/molecule.rb', line 531

def png_transformer(type_s, out_options={}, &block)
  orig_out_options = out_options[:size]
  if type_s == 'png'
    png_output = true
    type_s = 'svg'
    if out_options[:size]
      unless out_options[:size].to_s =~ /x/i
        out_options[:size] = out_options[:size].to_s + 'x' + out_options[:size].to_s
      end
    end
  else
    if out_options[:size].is_a?(String) && (out_options[:size] =~ /x/i)
      warn 'can only use the width dimension for this format'
      out_options[:size] = out_options[:size].split(/x/i).first
    end
  end
  image_blob = block.call(type_s, out_options)
  if png_output
    st = StringIO.new
    image = MiniMagick::Image.read(image_blob, 'svg')
    image.format('png')
    # would like to resize as an svg, then output the png of proper
    # granularity...
    image.resize(out_options[:size]) if out_options[:size]
    image_blob = image.write(st).string
  end
  out_options[:size] = orig_out_options
  image_blob
end

#remove_h!Object

returns self



252
253
254
255
# File 'lib/rubabel/molecule.rb', line 252

def remove_h!
  @ob.delete_hydrogens
  self
end

#smarts_indices(smarts_or_string, uniq = true) ⇒ Object

returns a list of atom indices matching the patterns (corresponds to the OBSmartsPattern::GetUMapList() method if uniq==true and GetMapList method if uniq==false). Note that the original GetUMapList returns atom numbers (i.e., the index + 1). This method returns the zero indexed indices.



157
158
159
160
161
162
# File 'lib/rubabel/molecule.rb', line 157

def smarts_indices(smarts_or_string, uniq=true)
  mthd = uniq ? :get_umap_list : :get_map_list
  pattern = smarts_or_string.is_a?(Rubabel::Smarts) ? smarts_or_string : Rubabel::Smarts.new(smarts_or_string)
  pattern.ob.match(@ob)
  pattern.ob.send(mthd).map {|atm_indices| atm_indices.map {|i| i - 1 } }
end

#smilesObject

returns just the smiles string :smi (not the id)



258
259
260
# File 'lib/rubabel/molecule.rb', line 258

def smiles
  to_s(:smi)
end

#spinObject



132
# File 'lib/rubabel/molecule.rb', line 132

def spin() @ob.get_total_spin_multiplicity end

#split(*bonds) ⇒ Object

splits the molecules at the given bonds and returns the fragments. Does not alter the caller. If the molecule is already fragmented, then returns the separate fragments.



444
445
446
447
448
449
450
451
452
# File 'lib/rubabel/molecule.rb', line 444

def split(*bonds)
  if bonds.size > 0
    delete_and_restore_bonds(*bonds) do |mol|
      mol.ob.separate.map(&:upcast)
    end
  else
    self.ob.separate.map(&:upcast)
  end
end

#strip_salts!Object

returns self



603
604
605
606
# File 'lib/rubabel/molecule.rb', line 603

def strip_salts!
  @ob.strip_salts!
  self
end

#swap!(anchor1, to_move1, anchor2, to_move2) ⇒ Object

swaps to_move1 for to_move2 on the respective anchors returns self



411
412
413
414
# File 'lib/rubabel/molecule.rb', line 411

def swap!(anchor1, to_move1, anchor2, to_move2)
  OpenBabel::OBBuilder.swap(@ob, *[anchor1, to_move1, anchor2, to_move2].map {|at| at.ob.get_idx } )
  self
end

#tanimoto(other, type = DEFAULT_FINGERPRINT) ⇒ Object

TODO: implement list of supported descriptors. (Not Yet Implemented!) def descs end



369
370
371
# File 'lib/rubabel/molecule.rb', line 369

def tanimoto(other, type=DEFAULT_FINGERPRINT)
  other.nil? ? 0 : Rubabel::Molecule.tanimoto(self, other, type)
end

#titleObject

attributes



126
# File 'lib/rubabel/molecule.rb', line 126

def title() @ob.get_title end

#title=(val) ⇒ Object



127
# File 'lib/rubabel/molecule.rb', line 127

def title=(val) @ob.set_title(val) end

#to_s(type = DEFAULT_OUT_TYPE) ⇒ Object

emits smiles without the trailing tab, newline, or id. Use write_string to get the default OpenBabel behavior (ie., tabs and newlines).



463
464
465
466
467
468
469
470
471
472
# File 'lib/rubabel/molecule.rb', line 463

def to_s(type=DEFAULT_OUT_TYPE)
  string = write_string(type)
  case type
  when :smi, :smiles, :can
    # remove name with openbabel options in the future
    string.split(/\s+/).first
  else
    string
  end
end

#write(filename_or_type = :can, out_options = {}) ⇒ Object

If filename_or_type is a symbol, then it will return a string of that type. If filename_or_type is a string, will write to the filename given no args, returns a DEFAULT_OUT_TYPE string



489
490
491
492
493
494
495
# File 'lib/rubabel/molecule.rb', line 489

def write(filename_or_type=:can, out_options={})
  if filename_or_type.is_a?(Symbol)
    write_string(filename_or_type, out_options)
  else
    write_file(filename_or_type, out_options)
  end
end

#write_file(filename, out_options = {}) ⇒ Object

writes to the file based on the extension given. If type is given explicitly, then it is used. If png is the extension or format, the png is generated from an svg.



574
575
576
577
# File 'lib/rubabel/molecule.rb', line 574

def write_file(filename, out_options={})
  type = Rubabel.filetype(filename)
  File.write(filename, write_string(type, out_options))
end

#write_string(type = DEFAULT_OUT_TYPE, out_options = {}) ⇒ Object

out_options include any of those defined



562
563
564
565
566
567
568
569
# File 'lib/rubabel/molecule.rb', line 562

def write_string(type=DEFAULT_OUT_TYPE, out_options={})
  png_transformer(type.to_s, out_options) do |type_s, _out_opts|
    obconv = out_options[:obconv] || OpenBabel::OBConversion.new
    obconv.set_out_format(type_s)
    obconv.add_opts!(:out, _out_opts)
    obconv.write_string(@ob)
  end
end