asciichem 0.3.4 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82e379bdd585e67154ee2ac11685d34d343fc7603dad41c8e629d27610f66a0e
4
- data.tar.gz: 443e0c865b127e706b61ec9d8781b3516aecb8dfb86a83630e1363af5ae52779
3
+ metadata.gz: 7d401ee7215f54961b4126b1da5451a2e71ac316c05e3864d199c5482e28d366
4
+ data.tar.gz: c132a8c376dd7485bf0319f8d971d89cb952c27d9f2466c0f3af4af945b0ca4b
5
5
  SHA512:
6
- metadata.gz: 1dca3abf1149dbbeb1166053cdc6afd58e90c4ad1635c5d13a8884aa11e00b1f43dbdadda6a860d5110dc3924f1ce8f01cba41992faaabbe5bd8a5b05382cdf6
7
- data.tar.gz: 2fb960db615687093e7cb30f70752303d1b143fc9c3af8cd582d0fef7afcff00e45feabe88769bbb8f6af6431171b3b431d2689339a1d52903f21f397acc23a3
6
+ metadata.gz: ab9ee8f2410f46c659cc97426a7272c511e983b601cbf043809c5b657621f0d7a48e79f17c2c4f79707cfebaf265e963077a5f636d8e03c87be2942e4fddc27e
7
+ data.tar.gz: 1cf4d97ef6459a29b9d7f05ddbd8b8b1db0ca512f547cb48badd7ebb5abc42f9c70c98cdade086af9dedf48f2e16c96ddd199097ee90c3e2ba56984e66a91aa7
@@ -0,0 +1,69 @@
1
+ # 01 — Crystallography: unit cell and space group
2
+
3
+ - **Priority:** P1
4
+ - **Status:** in progress
5
+
6
+ ## Motivation
7
+
8
+ Crystallography is the foundation of materials science. CML's
9
+ `<crystal>`, `<scalar>` (for a/b/c/α/β/γ), and `<symmetry>` elements
10
+ capture crystal structures. An ASCII encoding is dramatically more
11
+ readable than XML:
12
+
13
+ ```
14
+ crystal[NaCl](a=5.64,b=5.64,c=5.64,alpha=90,beta=90,gamma=90,sg=Fm-3m){
15
+ Na@f(0,0,0)
16
+ Cl@f(0.5,0.5,0.5)
17
+ }
18
+ ```
19
+
20
+ vs CML's verbose `<crystal><scalar title="a">5.64</scalar>...`.
21
+
22
+ ## Syntax design
23
+
24
+ ```
25
+ crystal[<name>](
26
+ a=<float>, b=<float>, c=<float>,
27
+ alpha=<float>, beta=<float>, gamma=<float>,
28
+ sg=<spacegroup>
29
+ ) {
30
+ <atoms with @f(x,y,z) fractional coords>
31
+ }
32
+ ```
33
+
34
+ - `crystal` is a keyword (not an element symbol — parsed before
35
+ molecule).
36
+ - Square brackets carry the optional crystal name/title.
37
+ - Parentheses carry unit cell parameters as `key=value` pairs.
38
+ - Curly braces carry the asymmetric unit atoms with fractional coords.
39
+ - `sg` is the Hermann-Mauguin space group symbol.
40
+
41
+ ## Model
42
+
43
+ ```ruby
44
+ class Crystal < Node
45
+ attr_accessor :name, :a, :b, :c, :alpha, :beta, :gamma,
46
+ :spacegroup, :atoms
47
+ end
48
+ ```
49
+
50
+ Atoms inside a crystal use `@f(x,y,z)` fractional coordinates
51
+ (already implemented in v0.3.4).
52
+
53
+ ## CML mapping
54
+
55
+ - `Crystal` → `<crystal>` with child `<scalar>` elements for cell
56
+ parameters and `<symmetry>` for space group.
57
+ - Atoms with fractional coords → `<atom xFract="..." yFract="..."`
58
+ zFract="..."/> inside `<atomArray>`.
59
+ - The crystal is a child of `<molecule>` (CML convention: crystal
60
+ structures live inside molecules).
61
+
62
+ ## Acceptance criteria
63
+
64
+ - [ ] `crystal[NaCl](...)` parses to a Crystal model node
65
+ - [ ] Text round-trip: `parse(s).to_text == s`
66
+ - [ ] CML round-trip: `parse(s).to_cml → parse → .to_text == s`
67
+ - [ ] Cell parameters carry through as CML `<scalar>` elements
68
+ - [ ] Space group carries as CML `<symmetry>` element
69
+ - [ ] Fractional coordinates on atoms (already supported)
@@ -0,0 +1,39 @@
1
+ # 02 — Spectroscopy: NMR / IR / MS spectra
2
+
3
+ - **Priority:** P2
4
+ - **Status:** pending
5
+
6
+ ## Syntax
7
+
8
+ ```
9
+ spectrum[nmr](type=1H,solvent=CDCl3,freq=400){
10
+ 1.2: 3H s "CH3"
11
+ 3.5: 2H q J=7 "CH2"
12
+ 7.2: 5H m "C6H5"
13
+ }
14
+
15
+ spectrum[ir]{
16
+ 3300: broad "O-H stretch"
17
+ 1700: strong "C=O stretch"
18
+ }
19
+
20
+ spectrum[ms]{
21
+ 18: 100% "M+"
22
+ 17: 23% "M-1"
23
+ }
24
+ ```
25
+
26
+ ## Model
27
+
28
+ ```ruby
29
+ class Spectrum < Node
30
+ attr_accessor :type, :params, :peaks
31
+ # peaks: [{position:, intensity:, multiplicity:, assignment:}, ...]
32
+ end
33
+ ```
34
+
35
+ ## CML mapping
36
+
37
+ Maps to `<spectrum>` with `<peakList>` containing `<peak>` children.
38
+ CML peak attributes: `xValue`, `xUnits`, `yValue`, `yUnits`,
39
+ `atomRefs`, `title`.
@@ -0,0 +1,31 @@
1
+ # 03 — Computational chemistry: QC calculation results
2
+
3
+ - **Priority:** P2
4
+ - **Status:** pending
5
+
6
+ ## Syntax
7
+
8
+ ```
9
+ calc(dft, b3lyp/6-31G*){
10
+ energy: -234.5 Hartree
11
+ dipole: [0.1, 0.2, 0.3] Debye
12
+ homo: -0.32 Hartree
13
+ lumo: 0.15 Hartree
14
+ gap: 0.47 Hartree
15
+ zpe: 0.05 Hartree
16
+ }
17
+ ```
18
+
19
+ ## Model
20
+
21
+ ```ruby
22
+ class Calculation < Node
23
+ attr_accessor :method, :basis, :properties
24
+ # properties: [{title:, value:, units:}, ...]
25
+ end
26
+ ```
27
+
28
+ ## CML mapping
29
+
30
+ Maps to `<module convention="convention:compchem">` containing
31
+ `<parameterList>` (method/basis) and `<propertyList>` (results).
@@ -0,0 +1,48 @@
1
+ # 04 — Structural extensions: Z-Matrix and fragments
2
+
3
+ - **Priority:** P3
4
+ - **Status:** pending
5
+
6
+ ## Syntax
7
+
8
+ ### Z-Matrix
9
+
10
+ ```
11
+ zmatrix{
12
+ C1
13
+ H2 C1 1.09
14
+ H3 C1 1.09 H2 109.5
15
+ H4 C1 1.09 H2 109.5 H3 120.0
16
+ }
17
+ ```
18
+
19
+ Each line: atom, reference atom, bond length, [reference atom, angle,
20
+ [reference atom, dihedral]].
21
+
22
+ ### Fragments
23
+
24
+ ```
25
+ fragment(phenyl){
26
+ C1-C2=C3-C4=C5-C6=1
27
+ }
28
+ ```
29
+
30
+ Named structural fragments that can be referenced by other molecules.
31
+
32
+ ## Model
33
+
34
+ ```ruby
35
+ class ZMatrix < Node
36
+ attr_accessor :rows
37
+ # rows: [{atom:, ref1:, r12:, ref2:, angle:, ref3:, dihedral:}, ...]
38
+ end
39
+
40
+ class Fragment < Node
41
+ attr_accessor :name, :molecule
42
+ end
43
+ ```
44
+
45
+ ## CML mapping
46
+
47
+ ZMatrix → `<zMatrix>` with rows as `<atom>` references.
48
+ Fragment → `<fragment>` containing a `<molecule>`.
@@ -0,0 +1,40 @@
1
+ # 05 — Reaction mechanisms and spectators
2
+
3
+ - **Priority:** P3
4
+ - **Status:** pending
5
+
6
+ ## Syntax
7
+
8
+ ### Reaction mechanism
9
+
10
+ ```
11
+ mechanism{
12
+ step1: Cl- + CH3Br -> [TS: Cl...C...Br] -> ClCH3 + Br-
13
+ step2: ClCH3 + Na+ -> CH3Cl + Na+
14
+ spectator: Na+
15
+ }
16
+ ```
17
+
18
+ ### Spectator ions
19
+
20
+ ```
21
+ Ag+(aq) + Cl-(aq) -> AgCl(s) | spectator: Na+(aq) NO3-(aq)
22
+ ```
23
+
24
+ Inline notation: `| spectator: <atoms>` after the reaction.
25
+
26
+ ## Model
27
+
28
+ ```ruby
29
+ class Mechanism < Node
30
+ attr_accessor :steps, :spectators, :reactive_centre
31
+ # steps: [{label:, transition_state:, reaction:}, ...]
32
+ end
33
+ ```
34
+
35
+ ## CML mapping
36
+
37
+ Mechanism → `<reactionScheme>` with `<reactionStepList>`
38
+ containing `<reactionStep>` children.
39
+ Spectators → `<spectatorList>` with `<spectator>` children.
40
+ ReactiveCentre → `<reactiveCentre>` with atom references.
@@ -0,0 +1,69 @@
1
+ # TODO.beyond-formulas index
2
+
3
+ Expanding AsciiChem beyond molecular formulas into the full CML
4
+ domain. Each workstream adds a new construct type with grammar,
5
+ model, formatter, and CML round-trip support.
6
+
7
+ ## Phase 1: Crystallography
8
+
9
+ | # | Title | Status |
10
+ |---|---|---|
11
+ | 01 | [Crystallography — unit cell and space group](01-crystallography.md) | **in progress** |
12
+
13
+ Syntax: `crystal[NaCl](a=5.64,b=5.64,c=5.64,alpha=90,beta=90,gamma=90,sg=Fm-3m){Na@f(0,0,0) Cl@f(0.5,0.5,0.5)}`
14
+
15
+ ## Phase 2: Spectroscopy
16
+
17
+ | # | Title | Status |
18
+ |---|---|---|
19
+ | 02 | [NMR / IR / MS spectra](02-spectroscopy.md) | pending |
20
+
21
+ Syntax:
22
+ ```
23
+ spectrum[nmr](type=1H,solvent=CDCl3){
24
+ 1.2: 3H s "CH3"
25
+ 7.2: 5H m "ArH"
26
+ }
27
+ ```
28
+
29
+ ## Phase 3: Computational Chemistry
30
+
31
+ | # | Title | Status |
32
+ |---|---|---|
33
+ | 03 | [QC calculation results](03-compchem.md) | pending |
34
+
35
+ Syntax: `calc(b3lyp/6-31G*){energy:-234.5 dipole:[0.1,0.2,0.3]}`
36
+
37
+ ## Phase 4: Structural Extensions
38
+
39
+ | # | Title | Status |
40
+ |---|---|---|
41
+ | 04 | [Z-Matrix and fragments](04-structural.md) | pending |
42
+
43
+ Syntax:
44
+ ```
45
+ zmatrix{C1; H2 C1 1.09; H3 C1 1.09 H2 109.5}
46
+ fragment(phenyl){C1-C2=C3-C4=C5-C6=1}
47
+ ```
48
+
49
+ ## Phase 5: Reaction Mechanisms
50
+
51
+ | # | Title | Status |
52
+ |---|---|---|
53
+ | 05 | [Mechanisms and spectators](05-mechanisms.md) | pending |
54
+
55
+ Syntax: `mechanism{step: A->[TS:B*]->C; spectator:Na+}`
56
+
57
+ ## Architecture
58
+
59
+ Each domain adds:
60
+ 1. A new `Model::*` class (autoload from `lib/asciichem/model/`)
61
+ 2. A grammar rule in `grammar.rb`
62
+ 3. A transform rule in `transform.rb`
63
+ 4. `visit_*` methods on formatters (Text, CML, others as needed)
64
+ 5. ModelAdapter mapping to `Chemicalml::Cml::*` wire classes
65
+ 6. Specs
66
+
67
+ The OCP principle: each domain is a self-contained construct that
68
+ plugs into the existing `Formula` → `nodes` array. No changes to
69
+ existing model classes.
@@ -123,6 +123,23 @@ module AsciiChem
123
123
  %("#{text.content}")
124
124
  end
125
125
 
126
+ def visit_crystal(crystal)
127
+ parts = ["crystal"]
128
+ parts << "[#{crystal.name}]" if crystal.name
129
+ params = []
130
+ params << "a=#{crystal.a}" if crystal.a
131
+ params << "b=#{crystal.b}" if crystal.b
132
+ params << "c=#{crystal.c}" if crystal.c
133
+ params << "alpha=#{crystal.alpha}" if crystal.alpha
134
+ params << "beta=#{crystal.beta}" if crystal.beta
135
+ params << "gamma=#{crystal.gamma}" if crystal.gamma
136
+ params << "sg=#{crystal.spacegroup}" if crystal.spacegroup
137
+ parts << "(#{params.join(',')})" unless params.empty?
138
+ atom_strs = crystal.atoms.map { |a| render_node(a) }
139
+ parts << "{#{atom_strs.join(' ')}}" unless atom_strs.empty?
140
+ parts.join
141
+ end
142
+
126
143
  private
127
144
 
128
145
  def render_node(node)
@@ -27,7 +27,29 @@ module AsciiChem
27
27
 
28
28
  rule(:nodes) { node >> (spaces? >> node).repeat }
29
29
 
30
- rule(:node) { reaction_cascade | reaction | electron_config | annotated_molecule | molecule | embedded_math | text_run.as(:text_run) }
30
+ rule(:node) { reaction_cascade | reaction | electron_config | crystal | annotated_molecule | molecule | embedded_math | text_run.as(:text_run) }
31
+
32
+ # -- crystallography -------------------------------------------------
33
+
34
+ # crystal[Name](a=X,b=Y,...,sg=SG){atoms with @f(x,y,z)}
35
+ rule(:crystal) do
36
+ str('crystal') >>
37
+ crystal_name.maybe >>
38
+ crystal_params.maybe >>
39
+ crystal_body.maybe
40
+ end
41
+
42
+ rule(:crystal_name) do
43
+ str('[') >> (str(']').absent? >> any).repeat.as(:crystal_name) >> str(']')
44
+ end
45
+
46
+ rule(:crystal_params) do
47
+ str('(') >> (str(')').absent? >> any).repeat.as(:crystal_params) >> str(')')
48
+ end
49
+
50
+ rule(:crystal_body) do
51
+ str('{') >> (str('}').absent? >> any).repeat.as(:crystal_body) >> str('}')
52
+ end
31
53
 
32
54
  # Annotated molecule: a molecule followed by one or more
33
55
  # `@key("value")` annotations for CML metadata (names,
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiChem
4
+ module Model
5
+ # A crystal structure: unit cell parameters + space group +
6
+ # asymmetric-unit atoms with fractional coordinates.
7
+ #
8
+ # Syntax:
9
+ # crystal[NaCl](a=5.64,b=5.64,c=5.64,alpha=90,beta=90,gamma=90,sg=Fm-3m){
10
+ # Na@f(0,0,0)
11
+ # Cl@f(0.5,0.5,0.5)
12
+ # }
13
+ class Crystal < Node
14
+ attr_accessor :name, :a, :b, :c, :alpha, :beta, :gamma,
15
+ :spacegroup, :atoms
16
+
17
+ def initialize(name: nil, a: nil, b: nil, c: nil,
18
+ alpha: nil, beta: nil, gamma: nil,
19
+ spacegroup: nil, atoms: [])
20
+ @name = name
21
+ @a = a
22
+ @b = b
23
+ @c = c
24
+ @alpha = alpha
25
+ @beta = beta
26
+ @gamma = gamma
27
+ @spacegroup = spacegroup
28
+ @atoms = atoms
29
+ end
30
+
31
+ def value_attributes
32
+ { name: name, a: a, b: b, c: c, alpha: alpha,
33
+ beta: beta, gamma: gamma, spacegroup: spacegroup,
34
+ atoms: atoms }
35
+ end
36
+
37
+ def children
38
+ atoms
39
+ end
40
+
41
+ def diagnostic_label
42
+ "Crystal(#{name || 'unnamed'})"
43
+ end
44
+
45
+ def to_s
46
+ params = []
47
+ params << "a=#{a}" if a
48
+ params << "b=#{b}" if b
49
+ params << "c=#{c}" if c
50
+ params << "alpha=#{alpha}" if alpha
51
+ params << "beta=#{beta}" if beta
52
+ params << "gamma=#{gamma}" if gamma
53
+ params << "sg=#{spacegroup}" if spacegroup
54
+ "crystal[#{name}](#{params.join(',')}){#{atoms.map(&:to_s).join(' ')}}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -8,6 +8,7 @@ module AsciiChem
8
8
  module Model
9
9
  autoload :Atom, "asciichem/model/atom"
10
10
  autoload :Bond, "asciichem/model/bond"
11
+ autoload :Crystal, "asciichem/model/crystal"
11
12
  autoload :ElectronConfiguration, "asciichem/model/electron_configuration"
12
13
  autoload :EmbeddedMath, "asciichem/model/embedded_math"
13
14
  autoload :Formula, "asciichem/model/formula"
@@ -146,8 +146,75 @@ module AsciiChem
146
146
  Model::ElectronConfiguration.new(orbitals: pairs)
147
147
  end
148
148
 
149
+ # -- crystals -------------------------------------------------------
150
+ #
151
+ # Grammar captures crystal_name, crystal_params, and crystal_body
152
+ # as optional strings. CrystalBuilder parses them into the model.
153
+
154
+ rule(crystal_name: subtree(:name),
155
+ crystal_params: subtree(:params),
156
+ crystal_body: subtree(:body)) do
157
+ CrystalBuilder.new(name, params, body).build
158
+ end
159
+
149
160
  # -- internal helpers ------------------------------------------------
150
161
 
162
+ # Builds a Crystal from parsed grammar captures. The grammar
163
+ # captures the name, params, and body as raw strings; this class
164
+ # parses them into the model fields.
165
+ class CrystalBuilder
166
+ def initialize(name, params_str, body_str)
167
+ @name = strip_parslet(name)
168
+ @params_str = strip_parslet(params_str)
169
+ @body_str = strip_parslet(body_str)
170
+ end
171
+
172
+ def build
173
+ params = parse_params(@params_str)
174
+ atoms = parse_atoms(@body_str)
175
+ Model::Crystal.new(
176
+ name: @name,
177
+ a: params['a'],
178
+ b: params['b'],
179
+ c: params['c'],
180
+ alpha: params['alpha'],
181
+ beta: params['beta'],
182
+ gamma: params['gamma'],
183
+ spacegroup: params['sg'],
184
+ atoms: atoms
185
+ )
186
+ end
187
+
188
+ private
189
+
190
+ def strip_parslet(value)
191
+ return nil if value.nil?
192
+
193
+ s = value.to_s.strip
194
+ s.empty? ? nil : s
195
+ end
196
+
197
+ def parse_params(str)
198
+ return {} unless str
199
+
200
+ str.split(',').each_with_object({}) do |pair, memo|
201
+ key, val = pair.strip.split('=', 2)
202
+ memo[key] = val&.strip if key
203
+ end
204
+ end
205
+
206
+ def parse_atoms(str)
207
+ return [] unless str
208
+
209
+ formula = AsciiChem.parse(str)
210
+ formula.nodes.flat_map do |node|
211
+ next [] unless node.is_a?(Model::Molecule)
212
+
213
+ node.nodes.select { |n| n.is_a?(Model::Atom) }
214
+ end
215
+ end
216
+ end
217
+
151
218
  # Strips the surrounding `"..."` quotes from a quoted text match.
152
219
  # Used by both `text_run` and `group_text_run` rules so the
153
220
  # model never carries the delimiters — the formatter re-adds them
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AsciiChem
4
- VERSION = "0.3.4"
4
+ VERSION = "0.4.0"
5
5
  end
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.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -114,6 +114,12 @@ files:
114
114
  - README.adoc
115
115
  - RELEASING.md
116
116
  - Rakefile
117
+ - TODO.beyond-formulas/01-crystallography.md
118
+ - TODO.beyond-formulas/02-spectroscopy.md
119
+ - TODO.beyond-formulas/03-compchem.md
120
+ - TODO.beyond-formulas/04-structural.md
121
+ - TODO.beyond-formulas/05-mechanisms.md
122
+ - TODO.beyond-formulas/README.md
117
123
  - asciichem.gemspec
118
124
  - benchmarks/RESULTS.md
119
125
  - benchmarks/benchmark.rb
@@ -149,6 +155,7 @@ files:
149
155
  - lib/asciichem/model.rb
150
156
  - lib/asciichem/model/atom.rb
151
157
  - lib/asciichem/model/bond.rb
158
+ - lib/asciichem/model/crystal.rb
152
159
  - lib/asciichem/model/electron_configuration.rb
153
160
  - lib/asciichem/model/embedded_math.rb
154
161
  - lib/asciichem/model/formula.rb