asciichem 0.31.0 → 0.31.2
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 +4 -4
- data/CHANGELOG.md +31 -0
- data/Gemfile +1 -1
- data/asciichem.gemspec +1 -1
- data/lib/asciichem/engine/parg/asciichem.json +1 -1
- data/lib/asciichem/engine/parg_engine.rb +1 -1
- data/lib/asciichem/formatter/text.rb +6 -0
- data/lib/asciichem/grammar_rules.rb +10 -1
- data/lib/asciichem/linter/valence_check.rb +10 -42
- data/lib/asciichem/model/atom.rb +4 -1
- data/lib/asciichem/model/bond.rb +25 -17
- data/lib/asciichem/model/reaction.rb +18 -0
- data/lib/asciichem/perception.rb +117 -0
- data/lib/asciichem/reaction_roles.rb +43 -0
- data/lib/asciichem/resolver/opsin.rb +68 -0
- data/lib/asciichem/resolver.rb +1 -0
- data/lib/asciichem/smiles/parser.rb +67 -35
- data/lib/asciichem/smiles/writer.rb +99 -34
- data/lib/asciichem/structure/graph.rb +3 -2
- data/lib/asciichem/structure/linearizer.rb +6 -3
- data/lib/asciichem/transform_rules.rb +6 -1
- data/lib/asciichem/version.rb +1 -1
- data/lib/asciichem/wire/core.rb +4 -0
- data/lib/asciichem/wire_adapter.rb +8 -4
- data/lib/asciichem.rb +2 -0
- data/scripts/generate-conformance-cases +57 -0
- metadata +7 -3
|
@@ -21,7 +21,7 @@ module AsciiChem
|
|
|
21
21
|
LOWERCASE_AROMATIC = %w[C N O S P B Se As].freeze
|
|
22
22
|
ORGANIC = %w[B C N O P S F Cl Br I].freeze
|
|
23
23
|
BOND_TOKENS = {
|
|
24
|
-
single:
|
|
24
|
+
single: '-', double: '=', triple: '#', quadruple: '$', aromatic: ':'
|
|
25
25
|
}.freeze
|
|
26
26
|
CONTINUATION_BONDS = %i[single aromatic].freeze
|
|
27
27
|
|
|
@@ -33,9 +33,7 @@ module AsciiChem
|
|
|
33
33
|
atoms, edges = Structure::Graph.build(@molecule)
|
|
34
34
|
# A single-atom component is valid SMILES (water "O", ions);
|
|
35
35
|
# multiple atoms with no bonds is a formula, not a structure.
|
|
36
|
-
if edges.empty? && atoms.length > 1
|
|
37
|
-
raise ParseError, "molecule has no bonds — a formula is not a structure"
|
|
38
|
-
end
|
|
36
|
+
raise ParseError, 'molecule has no bonds — a formula is not a structure' if edges.empty? && atoms.length > 1
|
|
39
37
|
|
|
40
38
|
@atoms = atoms
|
|
41
39
|
build_adjacency(edges)
|
|
@@ -53,6 +51,7 @@ module AsciiChem
|
|
|
53
51
|
|
|
54
52
|
build_tree(0, nil)
|
|
55
53
|
compute_tree_size(0)
|
|
54
|
+
compute_written_orders
|
|
56
55
|
emit(0, nil, nil)
|
|
57
56
|
|
|
58
57
|
@units.map do |unit|
|
|
@@ -69,10 +68,60 @@ module AsciiChem
|
|
|
69
68
|
|
|
70
69
|
def build_adjacency(edges)
|
|
71
70
|
@adjacency = Array.new(@atoms.length) { {} }
|
|
71
|
+
@directions = {}
|
|
72
|
+
@model_neighbours = Array.new(@atoms.length) { [] }
|
|
72
73
|
edges.each do |edge|
|
|
74
|
+
key = [edge.from, edge.to]
|
|
73
75
|
@adjacency[edge.from][edge.to] = edge.kind
|
|
74
76
|
@adjacency[edge.to][edge.from] = edge.kind
|
|
77
|
+
key = key.minmax
|
|
78
|
+
@directions[key] = edge.direction if edge.direction
|
|
79
|
+
# The edge array is in model node order: each atom's
|
|
80
|
+
# reference neighbours, for SMILES parity bookkeeping.
|
|
81
|
+
@model_neighbours[edge.from] << edge.to
|
|
82
|
+
@model_neighbours[edge.to] << edge.from
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The order neighbours are WRITTEN in: parent first, then ring
|
|
87
|
+
# closures, then branches (rank order), then the continuation.
|
|
88
|
+
# Tetrahedral parity flips with odd permutations of this order
|
|
89
|
+
# against the model's reference order, so the emitted @/@@ is
|
|
90
|
+
# corrected per atom. Ring-closure neighbours in the reference
|
|
91
|
+
# order are slice-3 scope (acyclic stereo is corrected here).
|
|
92
|
+
def compute_written_orders
|
|
93
|
+
@written_order = Array.new(@atoms.length)
|
|
94
|
+
walk = lambda do |index, parent|
|
|
95
|
+
order = parent ? [parent] : []
|
|
96
|
+
order += @closures[index].dup
|
|
97
|
+
# Appearance order: branches (rank order), then the
|
|
98
|
+
# continuation — emit() writes them that way.
|
|
99
|
+
ordered = @tree_children[index].sort_by { |child| continuation_rank(index, child) }
|
|
100
|
+
order += ordered[1..] + [ordered.first] unless ordered.empty?
|
|
101
|
+
@written_order[index] = order
|
|
102
|
+
@tree_children[index].each { |child| walk.call(child, index) }
|
|
103
|
+
end
|
|
104
|
+
walk.call(0, nil)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# TH1 -> TH2 flip when the written order is an odd permutation
|
|
108
|
+
# of the model's reference order (implicit hydrogens sort first
|
|
109
|
+
# on both sides).
|
|
110
|
+
def chirality_token(index)
|
|
111
|
+
chirality = @atoms[index].chirality
|
|
112
|
+
return '' unless chirality
|
|
113
|
+
|
|
114
|
+
reference = ([:h] if @atoms[index].hydrogens) || []
|
|
115
|
+
reference += @model_neighbours[index]
|
|
116
|
+
written = ([:h] if @atoms[index].hydrogens) || []
|
|
117
|
+
written += @written_order[index]
|
|
118
|
+
inversions = 0
|
|
119
|
+
written.each_with_index do |item, i|
|
|
120
|
+
written[(i + 1)..].each do |later|
|
|
121
|
+
inversions += 1 if reference.index(item) > reference.index(later)
|
|
122
|
+
end
|
|
75
123
|
end
|
|
124
|
+
chirality == 'TH2' || inversions.odd? ? '@@' : '@'
|
|
76
125
|
end
|
|
77
126
|
|
|
78
127
|
# Post-order subtree sizes within the DFS tree.
|
|
@@ -111,9 +160,9 @@ module AsciiChem
|
|
|
111
160
|
continuation, *branches = ordered
|
|
112
161
|
|
|
113
162
|
branches.each do |child|
|
|
114
|
-
@units << [:literal,
|
|
163
|
+
@units << [:literal, '(']
|
|
115
164
|
emit(child, index, @adjacency[index][child])
|
|
116
|
-
@units << [:literal,
|
|
165
|
+
@units << [:literal, ')']
|
|
117
166
|
end
|
|
118
167
|
emit(continuation, index, @adjacency[index][continuation])
|
|
119
168
|
end
|
|
@@ -125,7 +174,10 @@ module AsciiChem
|
|
|
125
174
|
def continuation_rank(index, child)
|
|
126
175
|
kind = @adjacency[index][child]
|
|
127
176
|
priority = CONTINUATION_BONDS.include?(kind) ? 0 : 1
|
|
128
|
-
|
|
177
|
+
# The order-independent token: this rank runs inside the
|
|
178
|
+
# written-order pre-pass, where stereo tokens are not yet
|
|
179
|
+
# computable (and stereo does not affect the order).
|
|
180
|
+
[priority, -@tree_size[child], atom_token_for(child, stereo: false)]
|
|
129
181
|
end
|
|
130
182
|
|
|
131
183
|
def ring_digit_for(a, b)
|
|
@@ -133,7 +185,7 @@ module AsciiChem
|
|
|
133
185
|
digit = @digit_by_edge[key]
|
|
134
186
|
return digit if digit
|
|
135
187
|
|
|
136
|
-
raise ParseError,
|
|
188
|
+
raise ParseError, 'too many ring closures for SMILES output' if @next_digit > 9
|
|
137
189
|
|
|
138
190
|
digit = @next_digit.to_s
|
|
139
191
|
@next_digit += 1
|
|
@@ -145,48 +197,61 @@ module AsciiChem
|
|
|
145
197
|
digit
|
|
146
198
|
end
|
|
147
199
|
|
|
148
|
-
|
|
149
200
|
def bond_token(kind, from, to)
|
|
150
201
|
token = BOND_TOKENS[kind]
|
|
151
202
|
raise ParseError, "#{kind} bonds have no SMILES form (v1 subset)" unless token
|
|
152
203
|
|
|
153
204
|
return token if kind != :single && kind != :aromatic
|
|
154
205
|
|
|
155
|
-
# Explicit only when the short form would change the meaning:
|
|
156
|
-
# a single bond between two aromatic atoms must be written;
|
|
157
|
-
# an aromatic bond is implicit between two aromatic atoms.
|
|
158
206
|
if kind == :single
|
|
159
|
-
|
|
207
|
+
direction = @directions[[from, to].minmax]
|
|
208
|
+
return direction == :up ? '/' : '\\' if direction
|
|
209
|
+
|
|
210
|
+
return '' unless aromatic?(from) && aromatic?(to)
|
|
211
|
+
|
|
212
|
+
# Explicit only when the short form would change the meaning:
|
|
213
|
+
# a single bond between two aromatic atoms must be written.
|
|
214
|
+
token
|
|
160
215
|
else
|
|
161
|
-
aromatic?(from) && aromatic?(to) ?
|
|
216
|
+
aromatic?(from) && aromatic?(to) ? '' : token
|
|
162
217
|
end
|
|
163
218
|
end
|
|
164
219
|
|
|
165
220
|
def atom_token(index)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
221
|
+
atom_token_for(index, stereo: true)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# `stereo: false` gives the order-independent form for ranking
|
|
225
|
+
# comparisons; the emitted token carries the corrected @/@@.
|
|
226
|
+
def atom_token_for(index, stereo:)
|
|
227
|
+
@plain_tokens ||= {}
|
|
228
|
+
return @plain_tokens[index] if !stereo && @plain_tokens[index]
|
|
229
|
+
|
|
230
|
+
atom = @atoms[index]
|
|
231
|
+
aromatic = atom.aromatic == true
|
|
232
|
+
lowercase = aromatic && LOWERCASE_AROMATIC.include?(atom.element)
|
|
233
|
+
needs_bracket =
|
|
234
|
+
atom.charge || atom.isotope || atom.hydrogens || atom.chirality ||
|
|
235
|
+
(!ORGANIC.include?(atom.element) && !lowercase) ||
|
|
236
|
+
(aromatic && !lowercase)
|
|
237
|
+
token =
|
|
238
|
+
if needs_bracket
|
|
178
239
|
symbol = lowercase ? atom.element.downcase : atom.element
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
240
|
+
t = +'['
|
|
241
|
+
t << atom.isotope if atom.isotope
|
|
242
|
+
t << symbol
|
|
243
|
+
t << chirality_token(index) if stereo && atom.chirality
|
|
182
244
|
if atom.hydrogens
|
|
183
|
-
|
|
184
|
-
|
|
245
|
+
t << 'H'
|
|
246
|
+
t << atom.hydrogens.to_s if atom.hydrogens > 1
|
|
185
247
|
end
|
|
186
|
-
|
|
187
|
-
|
|
248
|
+
t << charge_suffix(atom.charge) if atom.charge
|
|
249
|
+
t << ']'
|
|
250
|
+
else
|
|
251
|
+
bare_symbol(atom, lowercase)
|
|
188
252
|
end
|
|
189
|
-
|
|
253
|
+
@plain_tokens[index] = token unless stereo
|
|
254
|
+
token
|
|
190
255
|
end
|
|
191
256
|
|
|
192
257
|
def bare_symbol(atom, lowercase)
|
|
@@ -6,7 +6,7 @@ module AsciiChem
|
|
|
6
6
|
# atoms become edges (pending-bond semantics, same as the Layout
|
|
7
7
|
# walker); ring-closure digits become edges via AsciiChem::RingBonds.
|
|
8
8
|
module Graph
|
|
9
|
-
Edge = Struct.new(:from, :to, :kind, keyword_init: true)
|
|
9
|
+
Edge = Struct.new(:from, :to, :kind, :direction, keyword_init: true)
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
# Returns [atoms, edges] where atoms is the atom array in
|
|
@@ -29,7 +29,8 @@ module AsciiChem
|
|
|
29
29
|
index_by_object_id[node.object_id] = index
|
|
30
30
|
atoms << node
|
|
31
31
|
if pending && last
|
|
32
|
-
edges << Edge.new(from: last, to: index, kind: pending.kind
|
|
32
|
+
edges << Edge.new(from: last, to: index, kind: pending.kind,
|
|
33
|
+
direction: pending.direction)
|
|
33
34
|
end
|
|
34
35
|
last = index
|
|
35
36
|
pending = nil
|
|
@@ -32,7 +32,7 @@ module AsciiChem
|
|
|
32
32
|
@edges.each do |edge|
|
|
33
33
|
first, last = [edge.from, edge.to].minmax
|
|
34
34
|
if last - first == 1
|
|
35
|
-
@token_before[last] = edge
|
|
35
|
+
@token_before[last] = edge
|
|
36
36
|
else
|
|
37
37
|
digit = allocate_digit(first, last)
|
|
38
38
|
@digits_for[first] += digit
|
|
@@ -42,8 +42,11 @@ module AsciiChem
|
|
|
42
42
|
|
|
43
43
|
result = []
|
|
44
44
|
@atoms.each_with_index do |atom, index|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
edge = @token_before[index]
|
|
46
|
+
if index.positive? && edge
|
|
47
|
+
result << AsciiChem::Model::Bond.new(kind: edge.kind || :single,
|
|
48
|
+
direction: edge.direction)
|
|
49
|
+
end
|
|
47
50
|
atom.ring_closures = @digits_for[index] unless @digits_for[index].empty?
|
|
48
51
|
result << atom
|
|
49
52
|
end
|
|
@@ -62,6 +62,8 @@ module AsciiChem
|
|
|
62
62
|
|
|
63
63
|
# -- bonds ------------------------------------------------------------
|
|
64
64
|
|
|
65
|
+
rule(direction_up: simple(:_u)) { Model::Bond.new(kind: :single, direction: :up) }
|
|
66
|
+
rule(direction_down: simple(:_d)) { Model::Bond.new(kind: :single, direction: :down) }
|
|
65
67
|
rule(quadruple: simple(:_q)) { Model::Bond.new(kind: :quadruple) }
|
|
66
68
|
rule(triple: simple(:_t)) { Model::Bond.new(kind: :triple) }
|
|
67
69
|
rule(wedge: simple(:_w)) { Model::Bond.new(kind: :wedge) }
|
|
@@ -597,6 +599,7 @@ module AsciiChem
|
|
|
597
599
|
lone_pairs: lewis_count(hash[:lone_pairs]),
|
|
598
600
|
radical_electrons: lewis_count(hash[:radical_electrons]),
|
|
599
601
|
ring_closures: ring_closures_string(hash[:ring_closures]),
|
|
602
|
+
chirality: hash[:chirality]&.to_s,
|
|
600
603
|
x2: float_or_nil(hash[:x2]),
|
|
601
604
|
y2: float_or_nil(hash[:y2]),
|
|
602
605
|
z2: float_or_nil(hash[:z2]),
|
|
@@ -637,7 +640,7 @@ module AsciiChem
|
|
|
637
640
|
|
|
638
641
|
def initialize(element, isotope: nil, subscript: nil, superscript: nil,
|
|
639
642
|
lone_pairs: nil, radical_electrons: nil,
|
|
640
|
-
ring_closures: nil,
|
|
643
|
+
ring_closures: nil, chirality: nil,
|
|
641
644
|
x2: nil, y2: nil, z2: nil, atom_parity: nil,
|
|
642
645
|
spin_multiplicity: nil, atom_title: nil,
|
|
643
646
|
x_fract: nil, y_fract: nil, z_fract: nil)
|
|
@@ -648,6 +651,7 @@ module AsciiChem
|
|
|
648
651
|
@lone_pairs = lone_pairs
|
|
649
652
|
@radical_electrons = radical_electrons
|
|
650
653
|
@ring_closures = ring_closures
|
|
654
|
+
@chirality = chirality
|
|
651
655
|
@x2 = x2
|
|
652
656
|
@y2 = y2
|
|
653
657
|
@z2 = z2
|
|
@@ -672,6 +676,7 @@ module AsciiChem
|
|
|
672
676
|
lone_pairs: positive_int(@lone_pairs),
|
|
673
677
|
radical_electrons: positive_int(@radical_electrons),
|
|
674
678
|
ring_closures: @ring_closures,
|
|
679
|
+
chirality: @chirality,
|
|
675
680
|
x2: @x2,
|
|
676
681
|
y2: @y2,
|
|
677
682
|
z2: @z2,
|
data/lib/asciichem/version.rb
CHANGED
data/lib/asciichem/wire/core.rb
CHANGED
|
@@ -18,6 +18,7 @@ module AsciiChem
|
|
|
18
18
|
attribute :ring_closures, :string
|
|
19
19
|
attribute :aromatic, :boolean
|
|
20
20
|
attribute :hydrogens, :integer
|
|
21
|
+
attribute :chirality, :string
|
|
21
22
|
json do
|
|
22
23
|
map "element", to: :element
|
|
23
24
|
map "isotope", to: :isotope
|
|
@@ -28,6 +29,7 @@ module AsciiChem
|
|
|
28
29
|
map "radicalElectrons", to: :radical_electrons
|
|
29
30
|
map "ringClosures", to: :ring_closures
|
|
30
31
|
map "aromatic", to: :aromatic
|
|
32
|
+
map "chirality", to: :chirality
|
|
31
33
|
map "hydrogens", to: :hydrogens
|
|
32
34
|
end
|
|
33
35
|
end
|
|
@@ -90,8 +92,10 @@ module AsciiChem
|
|
|
90
92
|
wire_type "bond"
|
|
91
93
|
|
|
92
94
|
attribute :kind, :string
|
|
95
|
+
attribute :direction, :string
|
|
93
96
|
json do
|
|
94
97
|
map "kind", to: :kind
|
|
98
|
+
map "direction", to: :direction
|
|
95
99
|
end
|
|
96
100
|
end
|
|
97
101
|
end
|
|
@@ -107,7 +107,8 @@ module AsciiChem
|
|
|
107
107
|
radical_electrons: int_or_nil(atom.radical_electrons),
|
|
108
108
|
ring_closures: str_or_nil(atom.ring_closures),
|
|
109
109
|
aromatic: bool_or_nil(atom.aromatic),
|
|
110
|
-
hydrogens: int_or_nil(atom.hydrogens)
|
|
110
|
+
hydrogens: int_or_nil(atom.hydrogens),
|
|
111
|
+
chirality: str_or_nil(atom.chirality)
|
|
111
112
|
)
|
|
112
113
|
end
|
|
113
114
|
|
|
@@ -165,7 +166,8 @@ module AsciiChem
|
|
|
165
166
|
end
|
|
166
167
|
|
|
167
168
|
def bond_to_wire(node)
|
|
168
|
-
AsciiChem::Wire::Bond.new(type: "bond", kind: node.kind.to_s
|
|
169
|
+
AsciiChem::Wire::Bond.new(type: "bond", kind: node.kind.to_s,
|
|
170
|
+
direction: str_or_nil(node.direction&.to_s))
|
|
169
171
|
end
|
|
170
172
|
|
|
171
173
|
def identifier_to_wire(node)
|
|
@@ -302,7 +304,8 @@ module AsciiChem
|
|
|
302
304
|
charge: wire.charge, oxidation_state: wire.oxidation_state,
|
|
303
305
|
lone_pairs: wire.lone_pairs, radical_electrons: wire.radical_electrons,
|
|
304
306
|
ring_closures: wire.ring_closures,
|
|
305
|
-
aromatic: wire.aromatic, hydrogens: wire.hydrogens
|
|
307
|
+
aromatic: wire.aromatic, hydrogens: wire.hydrogens,
|
|
308
|
+
chirality: wire.chirality
|
|
306
309
|
)
|
|
307
310
|
end
|
|
308
311
|
|
|
@@ -324,7 +327,8 @@ module AsciiChem
|
|
|
324
327
|
end
|
|
325
328
|
|
|
326
329
|
def bond_from_wire(wire)
|
|
327
|
-
AsciiChem::Model::Bond.new(kind: wire.kind.to_sym
|
|
330
|
+
AsciiChem::Model::Bond.new(kind: wire.kind.to_sym,
|
|
331
|
+
direction: wire.direction&.to_sym)
|
|
328
332
|
end
|
|
329
333
|
|
|
330
334
|
def identifier_from_wire(wire)
|
data/lib/asciichem.rb
CHANGED
|
@@ -29,6 +29,8 @@ module AsciiChem
|
|
|
29
29
|
autoload :ModelAdapter, "asciichem/model_adapter"
|
|
30
30
|
autoload :Molfile, "asciichem/molfile"
|
|
31
31
|
autoload :Parser, "asciichem/parser"
|
|
32
|
+
autoload :Perception, 'asciichem/perception'
|
|
33
|
+
autoload :ReactionRoles, 'asciichem/reaction_roles'
|
|
32
34
|
autoload :PeriodicTable, "asciichem/periodic_table"
|
|
33
35
|
autoload :Resolver, "asciichem/resolver"
|
|
34
36
|
autoload :RingBonds, "asciichem/ring_bonds"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Differential conformance (TODO.v2/13 item 5): exports the property
|
|
5
|
+
# harness's generated models as wire JSON + canonical text so the
|
|
6
|
+
# TypeScript and Python implementations run the SAME cases (the
|
|
7
|
+
# cross-implementation differential gate). Deterministic seed; the
|
|
8
|
+
# output lives in asciichem-tests and is versioned like any corpus
|
|
9
|
+
# artifact.
|
|
10
|
+
#
|
|
11
|
+
# bundle exec ruby scripts/generate-conformance-cases \
|
|
12
|
+
# ../asciichem-tests/corpus/generated/models.json [count]
|
|
13
|
+
require "asciichem"
|
|
14
|
+
require "asciichem/wire_adapter"
|
|
15
|
+
require "fileutils"
|
|
16
|
+
require "json"
|
|
17
|
+
|
|
18
|
+
seed = 20_261_020
|
|
19
|
+
count = Integer(ARGV[1] || 300)
|
|
20
|
+
out_path = ARGV[0] || "../asciichem-tests/corpus/generated/models.json"
|
|
21
|
+
|
|
22
|
+
rng = Random.new(seed)
|
|
23
|
+
ELEMENTS = %w[H C N O F S P Cl].freeze
|
|
24
|
+
BOND_KINDS = %i[single double single triple].freeze
|
|
25
|
+
|
|
26
|
+
pick = ->(list) { list[rng.rand(list.length)] }
|
|
27
|
+
|
|
28
|
+
cases = []
|
|
29
|
+
count.times do
|
|
30
|
+
size = 2 + rng.rand(6)
|
|
31
|
+
atoms = Array.new(size) do
|
|
32
|
+
AsciiChem::Model::Atom.new(
|
|
33
|
+
element: pick.call(ELEMENTS),
|
|
34
|
+
charge: pick.call([nil, nil, nil, '2+', '-']),
|
|
35
|
+
chirality: pick.call([nil, nil, nil, nil, 'TH1', 'TH2'])
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
bonds = (1...size).map do
|
|
39
|
+
AsciiChem::Model::Bond.new(
|
|
40
|
+
kind: pick.call(BOND_KINDS),
|
|
41
|
+
direction: (pick.call([nil, nil, nil, :up, :down]) if rng.rand(6).zero?)
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
molecule = AsciiChem::Model::Molecule.new(nodes: atoms.zip(bonds).flatten.compact)
|
|
45
|
+
text = molecule.to_text
|
|
46
|
+
reparsed = AsciiChem.parse(text)
|
|
47
|
+
raise "seed #{seed} generated an unstable case: #{text.inspect}" unless reparsed.nodes.first == molecule
|
|
48
|
+
|
|
49
|
+
# The wire root is the formula document (the canonical root).
|
|
50
|
+
wire = AsciiChem::WireAdapter.to_wire(AsciiChem.parse(text))
|
|
51
|
+
# Framework serialization (lutaml-model), never hand-rolled.
|
|
52
|
+
cases << { "wire" => JSON.parse(wire.to_json), "text" => text }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
FileUtils.mkdir_p(File.dirname(out_path))
|
|
56
|
+
File.write(out_path, JSON.pretty_generate({ "seed" => seed, "cases" => cases }) + "\n")
|
|
57
|
+
puts "wrote #{cases.length} cases to #{out_path}"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asciichem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.31.
|
|
4
|
+
version: 0.31.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: '0.4'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: '0.4'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: elkrb
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -265,11 +265,14 @@ files:
|
|
|
265
265
|
- lib/asciichem/molfile/parser.rb
|
|
266
266
|
- lib/asciichem/molfile/writer.rb
|
|
267
267
|
- lib/asciichem/parser.rb
|
|
268
|
+
- lib/asciichem/perception.rb
|
|
268
269
|
- lib/asciichem/periodic_table.rb
|
|
270
|
+
- lib/asciichem/reaction_roles.rb
|
|
269
271
|
- lib/asciichem/resolver.rb
|
|
270
272
|
- lib/asciichem/resolver/adapter.rb
|
|
271
273
|
- lib/asciichem/resolver/cache.rb
|
|
272
274
|
- lib/asciichem/resolver/common_chemistry.rb
|
|
275
|
+
- lib/asciichem/resolver/opsin.rb
|
|
273
276
|
- lib/asciichem/resolver/pubchem.rb
|
|
274
277
|
- lib/asciichem/resolver/substance.rb
|
|
275
278
|
- lib/asciichem/ring_bonds.rb
|
|
@@ -290,6 +293,7 @@ files:
|
|
|
290
293
|
- lib/asciichem/wire/identity.rb
|
|
291
294
|
- lib/asciichem/wire_adapter.rb
|
|
292
295
|
- lib/asciichem/xml_builder.rb
|
|
296
|
+
- scripts/generate-conformance-cases
|
|
293
297
|
- scripts/update-model-schemas.sh
|
|
294
298
|
homepage: https://www.asciichem.org
|
|
295
299
|
licenses:
|