package ChemicalReaction; use Common; @ISA = qw(Common); use strict; #use warnings; use Math::Matrix; use Math::MatrixReal; use CSV; use Crystal::AtomType; #=============================================== # コンストラクタ、デストラクタ #=============================================== sub new { my ($module, $pDBFiles, $BaseDBDir) = @_; my $this = {}; bless $this; $this->Initialize(); $this->ReadDBFiles($pDBFiles, $BaseDBDir) if($pDBFiles ne ''); return $this; } sub DESTROY { my ($this) = @_; $this->SUPER::DESTROY(@_); } sub Initialize { my ($this, $InitializeDB) = @_; $InitializeDB = 1 if(!defined $InitializeDB); $this->ClearHashes(); if($InitializeDB) { delete $this->{pDBs}; } } sub ClearHashes { my ($this) = @_; $this->{pReagents} = []; $this->{Reagents} = []; $this->{ReagentsHash} = {}; $this->{pProducts} = []; $this->{Products} = []; $this->{ProductsHash} = {}; } #=============================================== # 一般関数 #=============================================== sub pDBs { my ($this)=@_; return $this->{pDBs}; }; sub DB { my ($this, $index)=@_; return $this->{pDBs}->[$index]; }; sub AtomDB { my ($this)=@_; return $this->{pAtomDB}; }; sub ElementaryDB { my ($this)=@_; return $this->{pElementaryDB}; }; sub CompoundDB { my ($this)=@_; return $this->{pCompoundDB}; }; sub InitialReaction { my ($this)=@_; return $this->{Initial} ; }; sub FinalReaction { my ($this)=@_; return $this->{Final} ; }; sub Reagents { my ($this)=@_; return $this->{Initial} ; }; sub Products { my ($this)=@_; return $this->{Final} ; }; sub ReagentsHash { my ($this)=@_; return $this->{ReagentsHash} ; }; sub ProductsHash { my ($this)=@_; return $this->{ProductsHash} ; }; sub pCompounds { my ($this, $key) = @_; if($key =~ /^P/i) { return $this->{ProductsHash}->{pCompounds}; } return $this->{ReagentsHash}->{pCompounds}; } sub nCompound { my ($this, $key, $compound) = @_; my $pHash; if($key =~ /^P/i) { $pHash = $this->ProductsHash(); } else { $pHash = $this->ReagentsHash(); } return $pHash->{$compound}{n}; } sub ElementsArray { my ($this, $key) = @_; return $this->pElements($key); } sub pElements { my ($this, $key) = @_; if($key =~ /^P/i) { return $this->{ProductsHash}->{pElements}; } return $this->{ReagentsHash}->{pElements}; } sub nElement { my ($this, $key, $element) = @_; my $pHash; if($key =~ /^P/i) { $pHash = $this->ProductsHash(); } else { $pHash = $this->ReagentsHash(); } return $pHash->{$element}{nElement}; } sub MolcularWeight { my ($this, $Compound) = @_; my $R = new ChemicalReaction; $R->AnalyzeAReaction('Reagents', $Compound); my $pHash = $R->ReagentsHash(); my $pElements = $R->ElementsArray("Reagents"); my $w = 0.0; for(my $i = 0 ; $i < @$pElements ; $i++) { my $e = $pElements->[$i]; my $n = $R->nElement("Reagents", $e); #print "$e: $n\n"; my $pAtomDB = AtomType::GetAtomDBHash($e); #print " Z=$pAtomDB->{Z}: M=$pAtomDB->{mass}\n"; $w += $n * $pAtomDB->{mass}; } return $w; } sub IsIncluded { my ($this, $key, $pArray, $CaseSensitive) = @_; return Utils::IsIncludedInArray($key, $pArray, $CaseSensitive); } sub SplitReaction { my ($this, $Reaction) = @_; return Utils::Split("=>?", $Reaction); } sub BuildReactionFromArrays { my ($this, $pCompounds, $pComposition, $format, $min, $Sort) = @_; $format = "%0.3f" if($format eq ''); $min = 1.0e-3 if(!defined $min); $Sort = 0 if(!defined $Sort); my $s = ''; for(my $i = 0 ; $i < @$pCompounds ; $i++) { next if($pComposition->[$i] < $min); my $n = sprintf($format, $pComposition->[$i]); $n =~ s/[\.0]+$//; $s .= $n . $pCompounds->[$i] . "+"; } if($Sort) { my @a = sort { return $a cmp $b; } Utils::Split("\\+", $s); $s = join('+', @a); } $s =~ s/\+$//; return $s; } sub SortChemicalFormula { my ($this, $formula) = @_; my ($pe, $pn) = $this->CompoundToElements($formula); my %n; for(my $i = 0 ; $i < @$pe ; $i++) { $n{$pe->[$i]} += $pn->[$i]; } my @a = sort { $a cmp $b; } keys %n; my $s = ''; for(my $i = 0 ; $i < @a ; $i++) { $s .= "$a[$i]$n{$a[$i]}"; } return $s; # return join('', @a); } sub ReactionToCompounds { my ($this, $reaction) = @_; my @Chemicals = Utils::Split("[\\+]", $reaction); my (@Compounds, @n); for(my $i = 0 ; $i < @Chemicals ; $i++) { ($n[$i], $Compounds[$i]) = ($Chemicals[$i] =~ /^([\d\.\/]*)(\D.*)$/); $n[$i] = 1 if($n[$i] eq ''); $n[$i] = eval($n[$i]); } return (\@Chemicals, \@Compounds, \@n); } sub CompoundToElements { my ($this, $Compound, $Debug) = @_; $Compound =~ s/:.*$//; $Compound =~ s/\-.*$//; $Compound =~ s/\(.*(AFM|FM|NM|NP|SP|NM|atom).*\)$//i; while(1) { print "C: $Compound\n" if($Debug); my ($head, $par, $n, $tail) = ($Compound =~ /^(.*?)[\(\[]([^\)\(\]\[]*)[\)\]](\d*)(.*)$/); last if(!defined $n); print " [$head][$par][$n][$tail]\n" if($Debug); my (@a) = Utils::FindAll("[A-Z][a-z]*\\d*", $par); my ($pe, $pn) = $this->CompoundToElements($par); my $s = ''; for(my $i = 0 ; $i < @$pe ; $i++) { my $n2 = $pn->[$i] * $n; $s .= "$pe->[$i]$n2"; } $Compound = "$head$s$tail"; } my (@a) = Utils::FindAll("[A-Z][a-z]*\\d*", $Compound); #print "a: ", join(',', @a), "\n"; # my (@Element, @n); my %nElement; for(my $i = 0 ; $i < @a ; $i++) { # ($Element[$i], $n[$i]) = ($a[$i] =~ /^([A-Z][a-z]*)(\d*)$/); # $n[$i] = 1 if(!defined $n[$i] or $n[$i] eq ''); my ($e, $n) = ($a[$i] =~ /^([A-Z][a-z]*)(\d*)$/); $n = 1 if(!defined $n or $n eq ''); $nElement{$e} += $n; } return ([keys %nElement], [values %nElement]); # return (\@Element, \@n); } sub ExtractPossibleCompounds { my ($this, $pElements, $pCompounds) = @_; $pElements = $this->pElements("Reagents") if(!defined $pElements); my $pCompoundDB = $this->CompoundDB(); $pCompounds = [keys %$pCompoundDB] if(!defined $pCompounds); my @PC; foreach my $c (@$pCompounds) { my ($pe, $pn) = $this->CompoundToElements($c); my $passed = 1; for(my $i = 0 ; $i < @$pe ; $i++) { if($this->IsIncluded($pe->[$i], $pElements)) { } else { $passed = 0; last; } } if($passed) { push(@PC, $c); } } $this->{pPossibleCompounds} = \@PC; return @PC; } sub AnalyzeAReaction { my ($this, $key, $reaction) = @_; my ($pChemicals, $pCompound, $pnCompound) = $this->ReactionToCompounds($reaction); my ($pReaction, $pList, $pHash); if($key =~ /^P/i or $key =~ /^F/i) { $this->{pProducts} = $pChemicals; $pList = $this->{Products}; $pHash = $this->{ProductsHash}; } else { $this->{pReagents} = $pChemicals; $pList = $this->{Reagents}; $pHash = $this->{ReagentsHash}; } #print "[$key]:\n"; my %ElementsHash; for(my $i = 0 ; $i < @$pCompound ; $i++) { #print "R[$i]=$pChemicals->[$i]\n"; my $compound = $pCompound->[$i]; my $n = $pnCompound->[$i]; $pList->[$i]{n} = $n; $pList->[$i]{Compound} = $compound; $pHash->{$compound}{n} = $n; #print " ${n}[$compound]: "; my ($pElement, $pnElement) = $this->CompoundToElements($compound); for(my $i = 0 ; $i < @$pElement ; $i++) { my $element = $pElement->[$i]; my $ne = $pnElement->[$i]; #print " $ne * $element + "; $ElementsHash{$element}++; $pHash->{$element}{ne} = $ne; $pHash->{$compound}{$element}{ne} += $ne; $pHash->{$element}{nElement} += $n * $ne; $pHash->{nElement}{$element} += $n * $ne; } #print "\n"; } #print "E: ", join(', ', keys %ElementsHash), "\n"; $pHash->{pCompounds} = $pCompound; $pHash->{pElements} = [keys %ElementsHash]; } sub Analyze { my ($this, $Reaction, $IsPrint) = @_; $IsPrint = 1 if(!defined $IsPrint); if($Reaction eq 'list') { $this->ListCompounds(); return (); } elsif($Reaction eq 'exit' or $Reaction eq 'quit' ) { exit; } $this->{Reaction} = $Reaction; ($this->{Initial}, $this->{Final}) = $this->SplitReaction($Reaction); # $this->{Initial} =~ s/\-/+\-/g; # $this->{Final} =~ s/\-/+\-/g; $this->AnalyzeAReaction("Reagents", $this->{Initial}); $this->PrintElements("Reagents") if($IsPrint); $this->AnalyzeAReaction("Products", $this->{Final}); $this->PrintElements("Products") if($IsPrint); } sub ListCompounds { my ($this) = @_; my @a = $this->SearchDBHit(".*"); for(my $i = 0 ; $i < @a ; $i++) { print "$i: $a[$i]\n"; } } sub CheckMassBalance { my ($this, $IsPrint) = @_; $IsPrint = 0 if(!defined $IsPrint); $this->PrintnElements("Reagents") if($IsPrint); $this->PrintnElements("Products") if($IsPrint); my $pnInitialElementHash = $this->{ReagentsHash}{nElement}; my $pnFinalElementHash = $this->{ProductsHash}{nElement}; foreach my $e (sort keys %$pnInitialElementHash) { if($pnInitialElementHash->{$e} != $pnFinalElementHash->{$e}) { print("Error: Mass balance violated for [$e]: " ."$pnInitialElementHash->{$e} != $pnFinalElementHash->{$e}\n") if($IsPrint); return 0; } } foreach my $e (sort keys %$pnFinalElementHash) { if($pnInitialElementHash->{$e} != $pnFinalElementHash->{$e}) { print("Error: Mass balance violated for [$e]: " ."$pnInitialElementHash->{$e} != $pnFinalElementHash->{$e}\n") if($IsPrint); return 0; } } return 1; } sub GetDBHash { my ($this, $Compound) = @_; for(my $i = 0 ; ; $i++) { my $DB = $this->DB($i); last if(!$DB); my $pHash = $DB->HashByXDataKey($Compound); return $pHash if($pHash and $pHash->{Status} eq ''); } return undef; } sub SearchDBHit { my ($this, $RegExp) = @_; my @Hit; for(my $i = 0 ; ; $i++) { my $DB = $this->DB($i); last if(!$DB); my $pX = $DB->{pX}; $pX = $DB->GetData(0) if(!defined $pX); my $pS = $DB->GetData("Status"); #print "ps=$pS\n"; my $nData = @$pX; for(my $i = 0 ; $i < $nData ; $i++) { if($pX->[$i] =~ /$RegExp/) { next if($pS and $pS->[$i] ne ''); push(@Hit, $pX->[$i]); } } } return @Hit; } sub MakePossibleComposition { my ($this, $pReagentElements, $pCompounds, $MaxNInReaction, $IsPrint) = @_; my $pCompoundDB = $this->CompoundDB(); my @Composition; my %nPElements; # # 試行的に組成比をつくる # for(my $i = 0 ; $i < @$pCompounds ; $i++) { my $c = $pCompounds->[$i]; my $Type = $pCompoundDB->{$c}->{Type}; #print "$c: $Type\n"; my $cp = 0.0; if($Type ne 'Elementary') { $cp = rand($MaxNInReaction); } $Composition[$i] = $cp; my ($pe, $pn) = $this->CompoundToElements($c); for(my $j = 0 ; $j < @$pe ; $j++) { $nPElements{$pe->[$j]} += $pn->[$j] * $Composition[$i]; } } return $this->RepairComposition(\@Composition, $pReagentElements, $pCompounds, $IsPrint); # # 一番多い元素の組成が反応物の組成を越えないように係数 $K を決める # my $K = 1.0; for(my $i = 0 ; $i < @$pReagentElements ; $i++) { my $e = $pReagentElements->[$i]; my $n = $nPElements{$e}; my $nElementInReaction = $this->nElement("Reagents", $e); my $k = $nElementInReaction / $n; $K = $k if($K > $k); } #print "K=$K\n"; for(my $i = 0 ; $i < @$pCompounds ; $i++) { $Composition[$i] *= $K; } foreach my $e (keys %nPElements) { $nPElements{$e} *= $K; } # # 不足元素分をElementaryで補う # for(my $i = 0 ; $i < @$pCompounds ; $i++) { my $c = $pCompounds->[$i]; my $Type = $pCompoundDB->{$c}->{Type}; next if($Type ne 'Elementary'); my $nElementInReaction = $this->nElement("Reagents", $c); my $nDiff = $nElementInReaction - $nPElements{$c}; $Composition[$i] = $nDiff; } my $TotalComposition = 0.0; for(my $i = 0 ; $i < @$pCompounds ; $i++) { #$Composition[$i] /= $TotalComposition; $TotalComposition += $Composition[$i]; } print "TotalComposition=$TotalComposition (K=$K)\n" if($IsPrint); return \@Composition; } sub RepairComposition { my ($this, $pComposition, $pReagentElements, $pCompounds, $IsPrint) = @_; my $pCompoundDB = $this->CompoundDB(); my %nPElements; for(my $i = 0 ; $i < @$pCompounds ; $i++) { my $c = $pCompounds->[$i]; my $Type = $pCompoundDB->{$c}->{Type}; if($pComposition->[$i] < 0.0) { $pComposition->[$i] = 0.0; } if($Type eq 'Elementary') { $pComposition->[$i] = 0.0; } my ($pe, $pn) = $this->CompoundToElements($c); for(my $j = 0 ; $j < @$pe ; $j++) { $nPElements{$pe->[$j]} += $pn->[$j] * $pComposition->[$i]; } } #print("rev: ", join(',', @$pComposition), "\n"); # # 一番多い元素の組成が反応物の組成を越えないように係数 $K を決める # my $K = 1.0; for(my $i = 0 ; $i < @$pReagentElements ; $i++) { my $e = $pReagentElements->[$i]; my $Type = $pCompoundDB->{$e}->{Type}; my $n = $nPElements{$e}; my $nElementInReaction = $this->nElement("Reagents", $e); #print "$e: $nElementInReaction\n"; my $k = ($n < 1e-10)? 1.0 : $nElementInReaction / $n; $K = $k if($K > $k); } #print "K=$K\n"; for(my $i = 0 ; $i < @$pCompounds ; $i++) { $pComposition->[$i] *= $K; } foreach my $e (keys %nPElements) { $nPElements{$e} *= $K; } #print("rev3: ", join(',', @$pComposition), "\n"); # return $pComposition; # # 不足元素分をElementaryで補う # for(my $i = 0 ; $i < @$pCompounds ; $i++) { my $c = $pCompounds->[$i]; my $Type = $pCompoundDB->{$c}->{Type}; #print "$i: $Type\n"; next if($Type ne 'Elementary'); my $nElementInReaction = $this->nElement("Reagents", $c); my $nDiff = $nElementInReaction - $nPElements{$c}; $pComposition->[$i] = $nDiff; } my $TotalComposition = 0.0; for(my $i = 0 ; $i < @$pCompounds ; $i++) { #$pComposition->[$i] /= $TotalComposition; $TotalComposition += $pComposition->[$i]; } print "TotalComposition=$TotalComposition (K=$K)\n" if($IsPrint); #print("rev2: ", join(',', @$pComposition), "\n"); return $pComposition } sub nElementByArray { my ($this, $pCompounds, $pComposition, $IsPrint) = @_; my %nFinalElement; for(my $i = 0 ; $i < @$pComposition ; $i++) { my $c = $pCompounds->[$i]; #print " $c: $pMinComposition->[$i]\n" if($IsPrint); my ($pElement, $pnElement) = $this->CompoundToElements($c); for(my $j = 0 ; $j < @$pElement ; $j++) { my $e = $pElement->[$j]; my $n = $pnElement->[$j]; $nFinalElement{$e} += $n * $pComposition->[$i]; } } return \%nFinalElement; } sub CalTotalEnergyByArray { my ($this, $pCompounds, $pComposition, $IsPrint) = @_; my $pCompoundDB = $this->CompoundDB(); my $Etot = 0.0; for(my $i = 0 ; $i < @$pCompounds ; $i++) { my $c = $pCompounds->[$i]; my $E = $pCompoundDB->{$c}->{Emol}; print " $pCompounds->[$i]: $pComposition->[$i] (E=$E)\n" if($IsPrint); $Etot += $E * $pComposition->[$i]; } return $Etot; } sub CalTotalEnergy { my ($this, $Reaction, $IsPrint) = @_; my $pCompoundDB = $this->CompoundDB(); my ($pRChemicals, $pRCompounds, $pRnCompounds) = $this->ReactionToCompounds($Reaction); my $EReaction = 0.0; for(my $i = 0 ; $i < @$pRCompounds ; $i++) { my $c = $pRCompounds->[$i]; my $n = $pRnCompounds->[$i]; # $c=F2などの場合 my ($Atom, $nAtom) = ($c =~ /^([A-Z][a-z]?)(\d*)$/); $c = $Atom if($Atom ne ''); $nAtom = 1 if(!defined $nAtom or $nAtom <= 0); my $E = $n * $nAtom * $pCompoundDB->{$c}->{Emol}; $EReaction += $E; $nAtom = '' if($nAtom == 1); print " C[$i]: $c$nAtom * $n (E=$pCompoundDB->{$c}->{Emol} * $n * $nAtom)\n" if($IsPrint); } return $EReaction; } sub CalEForm { my ($this) = @_; my @Reagents = @{$this->{pReagents}}; my @Products = @{$this->{pProducts}}; my $csv = $this->DB(); my $CouldNotFind = 0; my $EFormInitial = 0; for(my $i = 0 ; $i < @Reagents ; $i++) { my $n = $this->{Reagents}[$i]{n}; my $c = $this->{Reagents}[$i]{Compound}; my $pHash = $this->GetDBHash($c); if(!defined $pHash) { print "Error: Can not find [$c] in DBs.\n"; $this->ShowSpeculatedSpecies($c); $CouldNotFind = 1; next; # return (); } my $Z = $pHash->{Z}; my $Etot = $pHash->{Etot}; if(!defined $Etot) { print "Error: Can not find [$c] in DBs.\n"; $this->ShowSpeculatedSpecies($c); $CouldNotFind = 1; next; # return (); } $EFormInitial += $n * $Etot / $Z; } my $EFormFinal = 0; for(my $i = 0 ; $i < @Products ; $i++) { my $n = $this->{Products}[$i]{n}; my $c = $this->{Products}[$i]{Compound}; my $pHash = $this->GetDBHash($c); if(!defined $pHash) { print "Error: Can not find [$c] in DBs.\n"; $this->ShowSpeculatedSpecies($c); $CouldNotFind = 1; next; # return (); } my $Z = $pHash->{Z}; my $Etot = $pHash->{Etot}; if(!defined $Etot) { print "Error: Can not find [$c] in DBs.\n"; $this->ShowSpeculatedSpecies($c); $CouldNotFind = 1; next; # return (); } $EFormFinal += $n * $Etot / $Z; } if($CouldNotFind) { return (); } else { my $EForm = $EFormFinal - $EFormInitial; #print "EForm : $EForm eV ($EFormFinal - $EFormInitial)\n"; return ($EForm, $EFormInitial, $EFormFinal); } } sub ShowSpeculatedSpecies { my ($this, $Compound) = @_; my @Hit = $this->SearchDBHit("^${Compound}[\\-:]"); for(my $i = 0 ; $i < @Hit ; $i++) { print " $Hit[$i]\n"; } } sub ReadDBFile { my ($this, $DBFile, $BaseDBDir, $IsPrint) = @_; $IsPrint = 0 if(!defined $IsPrint); $this->{BaseDBDir} = $BaseDBDir if(defined $BaseDBDir); my $csv = new CSV; $DBFile = Deps::MakePath($BaseDBDir, $DBFile, 0) if(defined $BaseDBDir); if(!$csv->Read($DBFile, 0, 1)) { print "Error: $!: Can not read [$DBFile].\n"; return undef; } my $pX = $csv->GetXData("Compound"); if(!$pX) { print "Error: Can not find 'Compound' tag in [$DBFile].\n"; } for(my $i = 0 ; $i < @$pX ; $i++) { $pX->[$i] =~ s/\s+//g; $pX->[$i] =~ s/Atom/atom/gi; $pX->[$i] =~ s/\((AFM|FM|NM|NP|SP|NM|atom)\)/:$1/gi; $pX->[$i] =~ s/([A-Z].*)-/$1:/g; } $this->{pAtomDB} = {} if(!defined $this->{pAtomDB}); $this->{pElementaryDB} = {} if(!defined $this->{pElementaryDB}); $this->{pCompoundDB} = {} if(!defined $this->{pCompoundDB}); my $pAtom = $this->{pAtomDB}; my $pElementary = $this->{pElementaryDB}; my $pCompound = $this->{pCompoundDB}; for(my $i = 0 ; $i < @$pX ; $i++) { my $pHash = $csv->GetHashData($i); $pHash->{Z} = 1 if(!defined $pHash->{Z} or $pHash->{Z} == 0.0); $pHash->{Emol} = $pHash->{Etot} / $pHash->{Z} if(!defined $pHash->{Emol}); #print "$pHash->{Compound}: $pHash->{Etot} eV (Z=$pHash->{Z}) (Type=$pHash->{Type})\n"; if($pHash->{Type} eq 'Compound') { my $name = $pHash->{Compound}; $name =~ s/[:\-\s].*$//; $pCompound->{$name} = $pHash if(!defined $pCompound->{$name}); print " Compound: $name: $pHash->{Emol} eV\n" if($IsPrint); } if($pHash->{Type} eq 'Elementary') { my $name = $pHash->{Composition}; $name =~ s/[\d\s].*$//; $pCompound->{$name} = $pHash if(!defined $pCompound->{$name}); $pElementary->{$name} = $pHash if(!defined $pElementary->{$name}); print " Elementary: $name: $pHash->{Emol} eV\n" if($IsPrint); } if($pHash->{Type} eq 'Atom') { my $name = $pHash->{Composition}; $name =~ s/[\d\s].*$//; $pAtom->{$name} = $pHash if(!defined $pAtom->{$name}); print " Atom: $name: $pHash->{Emol} eV\n" if($IsPrint); } } return $csv; } sub ReadDBFiles { my ($this, $pDBFiles, $BaseDBDir) = @_; my @DBs; for(my $i = 0 ; $i < @$pDBFiles ; $i++) { #print "f=$pDBFiles->[$i]\n"; $DBs[$i] = $this->ReadDBFile($pDBFiles->[$i], $BaseDBDir); if(!$DBs[$i]) { return undef; } } $this->{pDBs} = \@DBs; return $this->{pDBs}; } sub PrintElements { my ($this, $key) = @_; my ($pReaction, $pList, $pHash); if($key =~ /^P/i or $key =~ /^F/i) { $pReaction = $this->{pProducts}; $pList = $this->{Products}; $pHash = $this->{ProductsHash}; } else { $pReaction = $this->{pReagents}; $pList = $this->{Reagents}; $pHash = $this->{ReagentsHash}; } print "$key:\n"; for(my $i = 0 ; $i < @$pReaction ; $i++) { my $n = $pList->[$i]{n}; my $compound = $pList->[$i]{Compound}; print " $n * $compound: "; my $IsFirst = 1; my $pElements = $pHash->{pElements}; foreach my $element (sort @$pElements) { # my $ne = $pHash->{$element}{ne}; my $ne = $pHash->{$compound}{$element}{ne};; next if($ne eq ''); my $nElement = $pHash->{$element}{nElement}; print " + " if(!$IsFirst); print "$ne * $element"; $IsFirst = 0; } print "\n"; } } sub PrintnElements { my ($this, $key) = @_; my $pnElementHash; if($key =~ /^P/i or $key =~ /^F/i) { $pnElementHash = $this->{ProductsHash}{nElement}; } else { $pnElementHash = $this->{ReagentsHash}{nElement}; } my $IsFirst = 1; print "nElements in $key: "; foreach my $e (sort keys %$pnElementHash) { print " + " if(!$IsFirst); print "$pnElementHash->{$e} * $e"; $IsFirst = 0; } print "\n"; } 1;