Chemistry-Harmonia
view release on metacpan or search on metacpan
lib/Chemistry/Harmonia.pm view on Meta::CPAN
if( $l > 1 ){
$k{ $_ } *= $l for keys %k;
}
# by GCD reduce order of coefficients
my $gcd = bgcd(values %k);
if( $gcd > 1 ){
$k{ $_ } /= $gcd for keys %k;
}
my $f;
for my $i (split /\*/){
$i =~ s/^(\d*\.?\d*)([\w\(\)\[\]\{\}]+)$/$k{$1}>1?"{$2}".$k{$1}:"{$2}"/e;
$f .= $i;
}
$_ = $f;
}
1 while s/[\(\{\[][\)\}\]]//g; # clean empty brackets
}
my $cf = [''];
for my $s (split /((?:\$|\d|\(|\)|\[|\]|\{|\})+)/,$substance){
if( $s =~ /[$sas]/i ){ # Atom symbols
&_in_gf($s, \@maU, \@maUl, $cf);
}elsif( $s =~ /\$/ ){ # Zero symbols: 'o' && 'O'
$s =~ s/^\$//;
&_add_zero( $cf, "O$s", "o$s" ); # add oxigen to { C H N P } -> { CO HO NO PO }
# add 'o' to { C H N P } -> { Co Ho No Po }
}elsif($s =~ /$mz/ && exists( $opts->{'zero2oxi'} ) ){ # Zero symbols
my $z = $s; # zero
$s =~ s/$mz/O/g; # oxigen
&_add_zero( $cf, $s, $z );
}else{ # Others
$_ .= $s for @$cf;
}
}
$cf->[0] =~ /\w/ ? return $cf : return;
}
sub _add_zero{
my( $cf, $oxi, $z ) = @_;
my $n = $#{$cf};
for( my $i = 0; $i <= $n; $i++ ){
$cf->[$i + $n + 1] = $cf->[$i].$oxi; # add oxigen to { C H N P } -> { CO HO NO PO }
$cf->[$i] .= $z; # add 'o' to { C H N P } -> { Co Ho No Po }
}
}
sub _in_gf{
my($s, $maU, $maUl, $cf) = @_;
# Unary symbols of fragment
my @i_maU = grep $s =~ /$_/i, @$maU;
if( length($s) == 1 ){ # Unary atom symbol
@i_maU || return;
$_ .= $i_maU[0] for @$cf;
return 1;
}
# Double symbols of fragment
( my @i_maUl = grep $s =~ /$_/i, @$maUl ) || @i_maU || return; # not present
# Strings of unary and double atom letters
my $saU = join '', @i_maU; # String of unary atom letters
my $saUl = join '|', @i_maUl; # 1th - CAPITAL, 2th - small
my @w; # Anchor symbols
# Unique unary symbols (is not present in double)
for( @i_maU ){
next if $saUl =~ /$_/i;
$s =~ s/\L$_/$_/g; # to CAPITAL
push @w, $_;
}
# Unique double (is not present in others)
for my $ul (@i_maUl){
(my $m = $saUl) =~ s/$ul//; # To copy and clean the tested
for(split //,$ul){
next if $m =~ /$_/i || $saU =~ /$_/i;
$s =~ s/$ul/$ul/ig;
push @w, $ul;
last;
}
}
$_ = $s;
if( length == 2){ # Two symbols
(my $salU = $saUl) =~ s/(\w+)/\l\U$1/g; # 1th - small, 2th - CAPITAL
# The order is important!!!
my @in_cf;
if( /$saUl/ || /[$saU]{2}/ ){ # Usual writing of atom: 1th - CAPITAL, 2th - small
# or both - CAPITAL unary letters
push @in_cf, $_;
}elsif( /\U$saUl/ ){ # 1th and 2th - CAPITAL letters of double symbol
push @in_cf, ucfirst lc;
}elsif( @i_maU && ( /[$saU][\L$saU\E]/ || /[\L$saU\E][$saU]/ ) ){ # For unary symbols:
# 1th - CAPITAL, 2th - small or 1th - small, 2th - CAPITAL letter
push @in_cf, uc;
}elsif( /$salU/ ){ # 1th - small, 2th - CAPITAL letter of double symbol
push @in_cf, ucfirst lc;
}elsif( length($saUl) == 0 ){ # Without double symbol
s/.?([$saU]+).?/$1/i; # unary symbol(s)
push @in_cf, uc;
}else{ # 1th and 2th small letters - two alternative variants
if( /[\L$saU]{2}/ ){ # unary symbols
push @in_cf, uc;
}
if( /\L$saUl/ ){ # double symbol
push @in_cf, ucfirst;
}
@in_cf || return;
if( @in_cf == 2 ){
my $n = $#{$cf};
for( my $i = 0; $i <= $n; $i++ ){
$cf->[$i + $n + 1] = $cf->[$i].$in_cf[1];
$cf->[$i] .= $in_cf[0];
}
return 1;
}
}
$_ .= $in_cf[0] for @$cf;
return 1;
}
# More 2 symbols
for( $s ){
my $m = join '|', $saUl, @i_maU;
while( ! /^(?:$m)/i ){ s/^.// } # Clear head
while( ! /(?:$m)$/i ){ s/.$// } # Clear tail
# Search double and unary symbols
my $z = '';
while( /^((?:$m)+)/g ){ $z .= $1 }
if( length $z ){
$_ .= $z for @$cf;
return 1 if length($z) == length; # The end
s/^$z//;
return &_in_gf($_, \@i_maU, \@i_maUl, $cf) if 3 > length;
}
}
if( @w ){ # Search by fragments of anchor symbols
my $m = join '|', @w;
for my $f (split /($m)/, $s){
&_in_gf($f, \@i_maU, \@i_maUl, $cf);
if( $#{$cf} ){ # For >1 formula
# To leave the longest fragments
for( my $i = $#$cf; $i > 0; $i-- ){
splice @$cf, $i, 1 if length( $cf->[$i] ) < length( $cf->[0] );
}
}
}
return 1;
}
# Difficult fragment
my $cf_copy = [ @$cf ];
$#{$cf} = -1; # Reset
_M_gf_1: # Letter by letter search
for( my $i = 1; $i < length($s); $i++ ){
my $cf_new = [ @$cf_copy ];
for( $s =~ /^(\w{$i})(\w+)/ ){
&_in_gf($_, \@i_maU, \@i_maUl, $cf_new) || next _M_gf_1;
}
# To accumulate only unique, big fragments
for my $k ( @$cf_new ){
scalar( grep length($_) > length($k) || $_ eq $k, @$cf ) || push @$cf, $k;
}
}
return 1;
}
# Tactico-technical characteristics of chemical reaction.
# Return quantity hash (SAR):
# 's'ubstance
# 'a'toms
# 'r'ank
sub ttc_reaction{
my $ce = shift || return;
# Hash atom matrix and quantity of atoms
my( $atoms_substance, $atoms ) = &_search_atoms_subs( $ce );
my $rank = Rank( [ values %$atoms_substance ] ) || return;
return { 's' => scalar( keys %$atoms_substance ),
'a' => $atoms,
'r' => $rank,
};
}
# Calculation common identifier for reaction:
# Alphabetic CLASS
# and
# Chemical Interger Reaction (CIR) identifier
# also
# brutto (gross) formulas of substances
sub class_cir_brutto{
my( $ce, $coef ) = @_;
$ce || return;
my( %elm, %bf, @cir );
for my $c ( @$ce ){
for my $s ( @$c ){
# stoichiometry coefficient
my $k = exists $coef->{$s} ? $coef->{$s} : 1;
my %e = Chemistry::File::Formula->parse_formula( $s );
# for brutto
$bf{ $s } = join '', map( "$_$e{$_}", sort{ $a cmp $b } keys %e );
# for cir
push @cir, $k.$bf{ $s };
# For class
@elm{ keys %e } = ''; # reaction atoms
}
}
# CLASS, CIR of reaction and hash: formula => brutto of substances
return [ join( '', sort { $a cmp $b } keys %elm ),
( run in 0.553 second using v1.01-cache-2.11-cpan-800906f7e73 )