(* ::Package:: *)

(************************************************************************)
(* This file was generated automatically by the Mathematica front end.  *)
(* It contains Initialization cells from a Notebook file, which         *)
(* typically will have the same name as this file except ending in      *)
(* ".nb" instead of ".m".                                               *)
(*                                                                      *)
(* This file is intended to be loaded into the Mathematica kernel using *)
(* the package loading commands Get or Needs.  Doing so is equivalent   *)
(* to using the Evaluate Initialization Cells menu command in the front *)
(* end.                                                                 *)
(*                                                                      *)
(* DO NOT EDIT THIS FILE.  This entire file is regenerated              *)
(* automatically each time the parent Notebook file is saved in the     *)
(* Mathematica front end.  Any changes you make to this file will be    *)
(* overwritten.                                                         *)
(************************************************************************)



(* ::Input::Initialization:: *)
BeginPackage["DataAnalysis`GeoChem`",{"DataAnalysis`"}];


(* ::Input::Initialization:: *)
Unprotect[Evaluate[$Context<>"*"]];


(* ::Input::Initialization:: *)
ConvertComps;
MinFormulaCations;
(*CombineFMO;*)
MolarMass;
RegisterComp;
RegisterOxide;
PhaseMolarMass;
ChemElementName;

MassToMoles;
MolesToMass;

MassFractionToMolality;
MassFractionTomMolality;
PpmToMolality;
PpmTomMolality;

MolalityToMassFraction;
MolalityToPpm;

OxideName;


(* ::Input::Initialization:: *)
FromMolarData;
ToMolarData;
ProjectFrom;
(*ReduceFe;
OxidizeFe;*)
ToFractions;
ToPercent;
Synonyms;


(* ::Input::Initialization:: *)
Begin["`Private`"];


(* ::Input::Initialization:: *)
SetAttributes[ErrorMessage,HoldFirst];
ErrorMessage[Args___]:=Module[
{},
Message[Args];
Abort[];
];

SetAttributes[WarningMessage,HoldFirst];
WarningMessage[Args___]:=Module[
{},
Message[Args];
];


(* ::Input::Initialization:: *)
SetAttributes[CatchError,HoldAll];
CatchError[Body_]:=(Catch[Body]===$ErrorFlag);


(* ::Input::Initialization:: *)
OxideCoefficients={};
CDefs=DataTable[<||>];


(* ::Input::Initialization:: *)
BuildCMatrix[]:=Module[{IDM,OM,CElements,Out,CDefsSyns,SynRow},(
Out=CDefs//ToArray;
Out=Out/.Missing[___]->0;

CElements=Out//First//Rest;

IDM=Prepend[IdentityMatrix[CElements//Length],CElements]//Transpose;

(Out~Join~IDM)//Transpose

(*COxides=CDefs[[1,2;;]];

IDM=Prepend[IdentityMatrix[COxides//Length],COxides]//Transpose;
OM=(ReplacePart[{#[[2]],-#[[4]]/2/#[[3]]}~Join~Table[0,{Length[COxides]-1}],(Position[COxides,#[[1]]]+1)\[Rule]1/#[[3]]]&/@(OxideCoefficients//Rest));

CDefsSyns={};
Scan[(
SynRow=Cases[CDefs,{#//First,__}]//First;

CDefsSyns=CDefsSyns~Join~((ReplacePart[SynRow,1\[Rule]#])&/@(#//Rest));
)&,CSynonyms];

Out=CDefs~Join~CDefsSyns;

Unprotect[MolarMass];

Scan[(MolarMass[#[[1]]]=MolarMass[#[[2]]]*#[[3]]+MolarMass["O"]*#[[4]])&,OxideCoefficients];

Scan[(MolarMass[#[[1]]]=Total[MapIndexed[MolarMass[(Out[[1,#2+1]]//First)]*#1&,#[[2;;]]]])&,(Out//Rest)];

Protect[MolarMass];

(*Total[MapIndexed[MolarMass[(Out[[#2+1,1]]//First)]*#1&,#[[2;;]]]]*)
(Out~Join~IDM~Join~OM)//Transpose*)
)];

(*CMatrix=BuildCMatrix[];*)


(* ::Input::Initialization:: *)
RegisterOxide::duplicate="The oxide `1` is already registered.";

RegisterOxide::usage="RegisterOxide[element,c,ox] registers the oxide with the chemical formula \!\(\*SubscriptBox[\(element\), \(c\)]\)\!\(\*SubscriptBox[\(O\), \(ox\)]\) for later use with other functions.";
RegisterOxide[element_,c_,o_]:=Module[{name},(
name=If[c>1&&StringMatchQ[element,__~~"+"~~DigitCharacter..],"("<>element<>")"<>ToString[c],element<>If[c>1,ToString[c],""]]<>"O"<>If[o>1,ToString[o],""];
If[MemberQ[OxideCoefficients,{name,_,_,_}],
ErrorMessage[RegisterOxide::duplicate,name];
];
If[StringMatchQ[element,__~~"+"~~DigitCharacter..],
(
RegisterComp[name,Synonyms->{(StringCases[element,LetterCharacter..]//First)<>If[c>1,ToString[c],""]<>"O"<>If[o>1,ToString[o],""]}];
AppendTo[OxideCoefficients,{(StringCases[element,LetterCharacter..]//First)<>If[c>1,ToString[c],""]<>"O"<>If[o>1,ToString[o],""],element,c,o}];
AppendTo[OxideCoefficients,{name,element,c,o}];
),
(
RegisterComp[name];
AppendTo[OxideCoefficients,{name,element,c,o}];
)
];
(*CDefs=JoinT[CDefs,{ReplacePart[Table[0,{Length[CDefs]}],1\[Rule]name]}//Transpose];*)

CMatrix=BuildCMatrix[];
)];


(* ::Input::Initialization:: *)
ParseChemicalFormula[str_]:=Module[
{Groups,FlatStr,Elements},
(
Groups=StringCases[str,("("~~x__~~")"~~f:(NumberString...)):>{x,If[f!="",ToExpression[f],1]}];
Groups=ParseChemicalFormula[#[[1]]]*#[[2]]&/@Groups;

FlatStr=StringReplace[str,("("~~__~~")"~~(NumberString...))->""];
Elements=StringCases[FlatStr,(e:(CharacterRange["A","Z"]~~CharacterRange["a","z"]...~~("+"~~NumberString...)...)~~(f:NumberString...)):>(e->If[f!="",ToExpression[f],1])];
Elements=Flatten@Join[Elements,Normal@Groups];

Elements=GroupBy[Elements,First->Last];
Elements=Total/@Elements;

KeyMap[If[!NumericQ[MolarMass[#]],ErrorMessage[RegisterComp::elementudef,#]]&,Elements];

Elements
)
]


(* ::Input::Initialization:: *)
Options[RegisterComp]={Synonyms->{}};
RegisterComp::elementudef="The element `1` is not known.";
RegisterComp::duplicate="The component `1` is already registered.";

RegisterComp::usage="RegisterComp[str] registers a chemical component with the chemical formula str for later use by other functions.";
RegisterComp[compdef_,OptionsPattern[]]:=Module[
{ElementList,MM},
(
If[CDefs[KeyMemberQ[compdef]],
ErrorMessage[RegisterComp::duplicate,compdef]
];

ElementList=ParseChemicalFormula[compdef];
MM=Total@(KeyValueMap[MolarMass[#1]*#2&,ElementList]);

Unprotect[MolarMass];

(If[CDefs[KeyMemberQ[#]],
ErrorMessage[RegisterComp::duplicate,#]
];
QueryTo[CDefs,Append[#->ElementList]];
MolarMass[#]=MM;
)&/@OptionValue[Synonyms];

QueryTo[CDefs,Append[compdef->ElementList]];
MolarMass[compdef]=MM;

Protect[MolarMass];

CMatrix=BuildCMatrix[];
)
]


(* ::Input::Initialization:: *)
MolarMass::undef="Molar mass of \"`1`\" is not defined.";
MolarMass::unknownunit="Unit `1` is not defined.";

MolarMass::usage="MolarMass[str] represents the molar mass of the element, species or compound str.";
MolarMass["Ac"]=227;
MolarMass["Ag"]=107.8682;
MolarMass["Al"]=26.9815386;
MolarMass["Am"]=243;
MolarMass["Ar"]=39.948;
MolarMass["As"]=74.92160;
MolarMass["At"]=210;
MolarMass["Au"]=196.966569;
MolarMass["B"]=10.811;
MolarMass["Ba"]=137.327;
MolarMass["Be"]=9.012182;
MolarMass["Bh"]=272;
MolarMass["Bi"]=208.98040;
MolarMass["Bk"]=247;
MolarMass["Br"]=79.904;
MolarMass["C"]=12.0107;
MolarMass["Ca"]=40.078;
MolarMass["Cd"]=112.411;
MolarMass["Ce"]=140.116;
MolarMass["Cf"]=251;
MolarMass["Cl"]=35.453;
MolarMass["Cm"]=247;
MolarMass["Cn"]=285;
MolarMass["Co"]=58.933195;
MolarMass["Cr"]=51.9961;
MolarMass["Cs"]=132.9054519;
MolarMass["Cu"]=63.546;
MolarMass["Db"]=268;
MolarMass["Ds"]=281;
MolarMass["Dy"]=162.500;
MolarMass["Er"]=167.259;
MolarMass["Es"]=252;
MolarMass["Eu"]=151.964;
MolarMass["F"]=18.9984032;
MolarMass["Fe"]=55.845;
MolarMass["Fe+2"]=55.845;
MolarMass["Fe+3"]=55.845;
MolarMass["Fm"]=257;
MolarMass["Fr"]=223;
MolarMass["Ga"]=69.723;
MolarMass["Gd"]=157.25;
MolarMass["Ge"]=72.64;
MolarMass["H"]=1.00794;
MolarMass["He"]=4.002602;
MolarMass["Hf"]=178.49;
MolarMass["Hg"]=200.59;
MolarMass["Ho"]=164.93032;
MolarMass["Hs"]=270;
MolarMass["I"]=126.90447;
MolarMass["In"]=114.818;
MolarMass["Ir"]=192.217;
MolarMass["K"]=39.0983;
MolarMass["Kr"]=83.798;
MolarMass["La"]=138.90547;
MolarMass["Li"]=6.941;
MolarMass["Lr"]=262;
MolarMass["Lu"]=174.9668;
MolarMass["Md"]=258;
MolarMass["Mg"]=24.3050;
MolarMass["Mn"]=54.938045;
MolarMass["Mo"]=95.96;
MolarMass["Mt"]=276;
MolarMass["N"]=14.0067;
MolarMass["Na"]=22.98976928;
MolarMass["Nb"]=92.90638;
MolarMass["Nd"]=144.242;
MolarMass["Ne"]=20.1791;
MolarMass["Ni"]=58.6934;
MolarMass["No"]=259;
MolarMass["Np"]=237;
MolarMass["O"]=15.9994;
MolarMass["Os"]=190.23;
MolarMass["P"]=30.973762;
MolarMass["Pa"]=231.03586;
MolarMass["Pb"]=207.2;
MolarMass["Pd"]=106.42;
MolarMass["Pm"]=145;
MolarMass["Po"]=209;
MolarMass["Pr"]=140.90765;
MolarMass["Pt"]=195.084;
MolarMass["Pu"]=244;
MolarMass["Ra"]=226;
MolarMass["Rb"]=85.4678;
MolarMass["Re"]=186.207;
MolarMass["Rf"]=265;
MolarMass["Rg"]=280;
MolarMass["Rh"]=102.90550;
MolarMass["Rn"]=222;
MolarMass["Ru"]=101.07;
MolarMass["S"]=32.065;
MolarMass["Sb"]=121.760;
MolarMass["Sc"]=44.955912;
MolarMass["Se"]=78.96;
MolarMass["Sg"]=271;
MolarMass["Si"]=28.0855;
MolarMass["Sm"]=150.36;
MolarMass["Sn"]=118.710;
MolarMass["Sr"]=87.62;
MolarMass["Ta"]=180.94788;
MolarMass["Tb"]=158.92535;
MolarMass["Tc"]=98;
MolarMass["Te"]=127.60;
MolarMass["Th"]=232.03806;
MolarMass["Ti"]=47.867;
MolarMass["Tl"]=204.3833;
MolarMass["Tm"]=168.93421;
MolarMass["U"]=238.02891;
MolarMass["Uuh"]=293;
MolarMass["Uuo"]=294;
MolarMass["Uup"]=288;
MolarMass["Uuq"]=289;
MolarMass["Uus"]=294;
MolarMass["Uut"]=284;
MolarMass["V"]=50.9415;
MolarMass["W"]=183.84;
MolarMass["Xe"]=131.293;
MolarMass["Y"]=88.90585;
MolarMass["Yb"]=173.054;
MolarMass["Zn"]=65.38;
MolarMass["Zr"]=91.224;


(* ::Input::Initialization:: *)
RegisterOxide@@#&/@{
{"Ca",1,1},
{"Si",1,2},
{"Mg",1,1},
{"Al",2,3},
{"Cr",2,3},
{"H",2,1},
{"C",1,2},
{"K",2,1},
{"Ti",1,2},
{"Fe+3",2,3},
{"Fe+2",1,1},
{"Na",2,1},
{"Mn",1,1},
{"P",2,5},
{"Li",2,1},
{"Sr",1,1},
{"S",1,3}
};


(* ::Input::Initialization:: *)
RegisterComp["CO3",Synonyms->{"Carbonate"}];
RegisterComp["CaCO3",Synonyms->{"Calcite"}];
RegisterComp["SO4",Synonyms->{"Sulfate"}];
RegisterComp["CaSO4",Synonyms->{"Anhydrite"}];


(* ::Input::Initialization:: *)
MassToMoles::usage="MassToMoles[comp,mass] converts a mass of a chemical component to a molar amount.
MassToMoles[datatable] convert the data in all columns of a DataTable from masses to molar amounts based on the component specified as column header.
MassToMoles[association] convert the data in elements of an Association from masses to molar amounts based on the component specified as keys.";
MassToMoles[comp_String,mass_]:=Module[
{M},
(
M=MolarMass[comp];
If[NumberQ[M],
Return[mass/M],
ErrorMessage[MolarMass::undef,comp]
];
)
];
MassToMoles[dat_DataTable]:=dat[All,Association@KeyValueMap[#1->(MassToMoles[#1,#2])&,#]&];
MassToMoles[assoc_Association]:=Association@KeyValueMap[#1->MassToMoles[#1,#2]&,assoc];

MolesToMass::usage="MolesToMass[comp,moles] converts a molar amount of a chemical component to a mass.
MolesToMass[datatable] convert the data in all columns of a DataTable from molar amounts to masses based on the component specified as column header.
MolesToMass[association] convert the data in elements of an Association from molar amounts to masses based on the component specified as keys.";
MolesToMass[comp_String,moles_]:=Module[
{M},
(
M=MolarMass[comp];
If[NumberQ[M],
Return[moles*M],
ErrorMessage[MolarMass::undef,comp]
];
)
];
MolesToMass[dat_DataTable]:=dat[All,Association@KeyValueMap[#1->(MolesToMass[#1,#2])&,#]&];
MolesToMass[assoc_Association]:=Association@KeyValueMap[#1->MolesToMass[#1,#2]&,assoc];


(* ::Input::Initialization:: *)
MassFractionToMolality::usage="MassFractionToMolality[<|\!\(\*SubscriptBox[\(comp\), \(1\)]\)\[Rule]\!\(\*SubscriptBox[\(mass\), \(1\)]\),\!\(\*SubscriptBox[\(comp\), \(2\)]\)\[Rule]\!\(\*SubscriptBox[\(mass\), \(2\)]\),...|>,prefix] converts mass fractions in an association where the keys are the component names (which can be suffixed by ' / ppm', etc.) and values are the mass fractions to molalities with the specified unit prefix (m,\[Mu],n,p), which is optional.
MassFractionToMolality[mass,comp] converts a mass fractions of a component to molality assuming that it is the only solute.";
MassFractionToMolality[solutes_Association,prefix_:None]:=Module[
{Sol,W0,M,El,F,Out},
(
Sol=Association@KeyValueMap[(
If[StringMatchQ[#1,WordCharacter..~~" / "~~WordCharacter..],
(
El=StringCases[#1,(StartOfString~~x__~~" / "~~WordCharacter..)->x]//First;

F=Switch[
StringCases[#1,(WordCharacter..~~" / "~~x__~~EndOfString)->x]//First,
"%",100,
"ppm",10^6,
"ppb",10^9,
"ppt",10^12,
"ppq",10^15,
_,ErrorMessage[MolarMass::unknownunit,StringCases[#1,(WordCharacter..~~" / "~~x__~~EndOfString)->x]//First]
];
),
(
El=#1;
F=1;
)
];
El->If[MissingQ[#2],#2,#2/F]
)&,solutes
];

W0=1-Total[DeleteMissing[Values[Sol],1,2]];
Out=Association@KeyValueMap[(#1~~" / mol/kg")->(
M=MolarMass[#1];
If[NumericQ[M],
If[MissingQ[#2],#2,#2*10^3/(W0*M)],
ErrorMessage[MolarMass::undef,#1]
]
)&,Sol];

Switch[prefix,
"m",Association@KeyValueMap[StringInsert[#1,"m",-7]->If[MissingQ[#2],#2,#2*10^3]&,Out],
"\[Mu]",Association@KeyValueMap[StringInsert[#1,"\[Mu]",-7]->If[MissingQ[#2],#2,#2*10^6]&,Out],
"n",Association@KeyValueMap[StringInsert[#1,"n",-7]->If[MissingQ[#2],#2,#2*10^9]&,Out],
"p",Association@KeyValueMap[StringInsert[#1,"p",-7]->If[MissingQ[#2],#2,#2*10^12]&,Out],
_,Out
]
)
];
MassFractionToMolality[val_,comp_String]:=MassFractionToMolality[<|comp->val|>][[1]];

MassFractionTomMolality::usage="MassFractionTomMolality[data] is a short version of MassFractionToMolality[data,\"m\"]";
MassFractionTomMolality[solutes_Association]:=MassFractionToMolality[solutes,"m"];
MassFractionTomMolality[val_,comp_String]:=MassFractionTomMolality[<|comp->val|>][[1]];

PpmToMolality::usage="PpmToMolality[args] evaluates MassFractionToMolality[args] after multiplying values by \!\(\*SuperscriptBox[\(10\), \(-6\)]\).";
PpmToMolality[solutes_Association,prefix_:None]:=MassFractionToMolality[If[MissingQ[#],#,#*10^-6]&/@solutes,prefix];

PpmTomMolality::usage="PpmTomMolality[data] evaluates MassFractionToMolality[data,\"m\"] after multiplying values by \!\(\*SuperscriptBox[\(10\), \(-6\)]\).";
PpmTomMolality[solutes_Association]:=PpmToMolality[solutes,"m"];

PpmToMolality[val_,comp_String]:=MassFractionToMolality[val*10^-6,comp];
PpmTomMolality[val_,comp_String]:=PpmTomMolality[<|comp->val|>][[1]];


(* ::Input::Initialization:: *)
MolalityToMassFraction::usage="MolalityToMassFraction[<|\!\(\*SubscriptBox[\(comp\), \(1\)]\)\[Rule]\!\(\*SubscriptBox[\(amount\), \(1\)]\),\!\(\*SubscriptBox[\(comp\), \(2\)]\)\[Rule]\!\(\*SubscriptBox[\(amount\), \(2\)]\),...|>,quantifier] converts molalities in an association where the keys are the component names (which can have a unit with unit prefix mmol/kg,\[Mu]mol/kg,nmol/kg,pmol/kg) and values are the molalities to mass fractions with the specified unit quantifier (ppm, etc.), which is optional.
MolalityToMassFraction[amount,comp] converts a molality of a component to a mass fraction assuming that it is the only solute.";
MolalityToMassFraction[solutes_Association,quant_:None]:=Module[
{Sol,W0,El,F,M,Out},
(
Sol=Association@KeyValueMap[(
If[StringMatchQ[#1,WordCharacter..~~" / "~~WordCharacter...~~"mol/kg"],
(
El=StringCases[#1,(StartOfString~~x__~~" / "~~WordCharacter..)->x]//First;

F=Switch[
StringCases[#1,(WordCharacter..~~" / "~~x___~~"mol/kg"~~EndOfString)->x]//First,
"m",10^3,
"\[Mu]",10^6,
"n",10^9,
"",1,
_,ErrorMessage[MolarMass::unknownunit,StringCases[#1,(WordCharacter..~~" / "~~x__~~"/kg"~~EndOfString)->x]//First]
];
),
(
El=#1;
F=1;
)
];
El->If[MissingQ[#2],#2,#2/F]
)&,solutes
];

W0=Total@KeyValueMap[(
M=MolarMass[#1];
If[NumberQ[M],
#2/10^3*M,
ErrorMessage[MolarMass::undef,#1]
]
)&,DeleteMissing[Sol]];
W0=1/(1+W0);

Out=Association@KeyValueMap[#1->(
M=MolarMass[#1];
If[MissingQ[#2],#2,W0*#2/10^3*M]
)&,Sol];

Switch[quant,
"%",Association@KeyValueMap[#1<>" / %"->If[MissingQ[#2],#2,#2*10^2]&,Out],
"ppm",Association@KeyValueMap[#1<>" / ppm"->If[MissingQ[#2],#2,#2*10^6]&,Out],
"ppb",Association@KeyValueMap[#1<>" / ppb"->If[MissingQ[#2],#2,#2*10^9]&,Out],
"ppt",Association@KeyValueMap[#1<>" / ppt"->If[MissingQ[#2],#2,#2*10^12]&,Out],
"ppq",Association@KeyValueMap[#1<>" / ppq"->If[MissingQ[#2],#2,#2*10^15]&,Out],
_,Out
]
)
];

MolalityToPpm::usage="MolalityToPpm[data] is a short version of MolalityToMassFraction[data,\"ppm\"]";
MolalityToPpm[solutes_Association]:=MolalityToMassFraction[solutes,"ppm"];
MolalityToMassFraction[val_,comp_String]:=MolalityToMassFraction[<|comp->val|>][[1]];
MolalityToPpm[val_,comp_String]:=MolalityToMassFraction[<|comp->val|>,"ppm"][[1]];


(* ::Input::Initialization:: *)
Options[ConvertComps]={(*ReduceFe\[Rule]Automatic,OxidizeFe\[Rule]Automatic,*)FromMolarData->False,ToMolarData->False,ToPercent->False,ToFractions->False,ProjectFrom->None};

(*ReduceFe::usage="ReduceFe is an option that specifies whether iron can be reduced in ConvertComp[...].";
OxidizeFe::usage="OxidizeFe is an option that specifies whether iron can be oxidized in ConvertComp[...].";*)
FromMolarData::usage="FromMolarData is an option that specifies the input data for ConvertComps[...] are molar amounts.";
ToMolarData::usage="ToMolarData is an option that specifies the output data for ConvertComps[...] should be molar amounts.";
ToPercent::usage="ToPercent is an option that specifies the output data for ConvertComps[...] should be percentages.";
ToFractions::usage="ToFractions is an option that specifies the output data for ConvertComps[...] should be fractions.";
ProjectFrom::usage="ProjectFrom is an option that specifies a list of components from which the data should be projected in ConvertComps[...].";


(* ::Input::Initialization:: *)

ConvertComps::compcount="More output components (`1`) are required to solve the conversation matrix. Maybe adding oxygen ('O') and projecting from this component using the ProjectFrom option helps? Are all custom components registered using RegisterComp?";
ConvertComps::unknowncomp="The target component \"`1`\" is not known. Custom compoments must be registered using RegisterComp before they can be used with ConvertComps.";
ConvertComps::excesscomp="The component(s) `1` is/are not necessary for the conversion to the target components and will not be used.";
ConvertComps::noinput="The input data does not contain any valid composition data.";

ConvertComps::usage="ConvertComps[datatable,{\!\(\*SubscriptBox[\(component\), \(1\)]\),\!\(\*SubscriptBox[\(component\), \(2\)]\),...}] converts the compositional data in a DataTable to a set of new chemical components using matrix transformations.";
ConvertComps[dat_DataTable,newComps_,OptionsPattern[]]:=Module[
{FromCols,ToCols,IData,A,Ox,OxRows,B,OData,Pos,IDColumnData},(
IData=dat;

If[!OptionValue[FromMolarData],
IData=MassToMoles[IData];
];

IData=IData//ToArray//Transpose//Rest;

If[Length[Select[IData[[All,1]],FreeQ[CMatrix[[1]],#]&]]>0,WarningMessage[ConvertComps::excesscomp,Select[IData[[All,1]],FreeQ[CMatrix[[1]],#]&]]];
IData=Select[IData,MemberQ[CMatrix[[1]],#[[1]]]&];

FromCols=((Position[CMatrix[[1]],#])&/@IData[[All,1]])//Flatten;
If[Length[FromCols]==0,ErrorMessage[ConvertComps::noinput];];

ToCols=((Position[CMatrix[[1]],#])&/@newComps);
MapIndexed[(
If[Length[#]==0,ErrorMessage[ConvertComps::unknowncomp,newComps[[#2[[1]]]]]]
)&,ToCols];
ToCols=ToCols//Flatten;

Ox=DeleteCases[(CMatrix[[All,Prepend[ToCols,1]]]),{_,0..}][[2;;,1]];
OxRows=((Position[CMatrix[[All,1]],#])&/@Ox)//Flatten;
If[(OxRows//Length )>(ToCols//Length),ErrorMessage[ConvertComps::compcount,(OxRows//Length )-(ToCols//Length)]];

A=CMatrix[[OxRows,FromCols]];
(*If[MemberQ[Ox,"Fe+2"]&&(OptionValu
e[ReduceFe]||(OptionValue[ReduceFe]\[Equal]Automatic&&MatchQ[A[[Position[Ox,"Fe+2"][[1,1]]]],{0..}])),
Pos=Position[Ox,"Fe+2"][[1,1]];
A[[Pos]]=Cases[CMatrix,{"Fe+3",__}][[1,FromCols]]+A[[Pos]];
(*If[MemberQ[Ox,"Fe2O3"],
Pos=Position[Ox,"Fe2O3"][[1,1]];
A[[Pos]]=ConstantArray[0,Length[OxRows]];
];*)
];
If[MemberQ[Ox,"Fe+3"]&&(OptionValue[OxidizeFe]||(OptionValue[OxidizeFe]\[Equal]Automatic&&MatchQ[A[[Position[Ox,"Fe+3"][[1,1]]]],{0..}])),
Pos=Position[Ox,"Fe+3"][[1,1]];
A[[Pos]]=Cases[CMatrix,{"Fe+2",__}][[1,FromCols]]+A[[Pos]];
];*)
MapIndexed[(If[MatchQ[#,{0..}],
WarningMessage[ConvertComps::excesscomp,IData[[#2[[1]],1]]]
])&,A//Transpose];

B=CMatrix[[OxRows,ToCols]]//Inverse;

OData=ReplacePart[(B.A.IData)//Transpose,1->newComps];

If[ListQ[OptionValue[ProjectFrom]],
OData=OData[[All,(Position[OData//First,#,{1},1]&/@Select[OData//First,!MemberQ[OptionValue[ProjectFrom],#]&])//Flatten]];
];

If[!OptionValue[ToMolarData],OData=MapIndexed[MolesToMass[OData[[1,#2//First]],#1]&,#]&/@OData//Rest//Prepend[#,OData//First]&];

If[OptionValue[ToFractions],OData=(SumNormalize/@OData//Rest)//Prepend[#,OData//First]&;];

If[OptionValue[ToPercent],OData=(SumNormalize[#]*100&/@OData//Rest)//Prepend[#,OData//First]&;];

OData=DataTable[Prepend[OData//Transpose,Prepend[NK[dat],"ID"]]//Transpose];
OData[All,All,Simplify@DVE@#&]
)];

ConvertComps[assoc_Association,newComps_,opts:OptionsPattern[]]:=If[Depth@DVMeans@assoc==2,
First@Normal@ConvertComps[DataTable[<|"1"->assoc|>],newComps,opts],
Normal@ConvertComps[DataTable[assoc],newComps,opts]
];


(* ::Input::Initialization:: *)
SumNormalize[list_List]:=list/Total[list];


(* ::Input::Initialization:: *)
NumberCations[oxide_]:=If[MemberQ[OxideCoefficients,{oxide,__}],
OxideCoefficients[[Position[OxideCoefficients,{oxide,__}]//First,3]]//First,
ErrorMessage[ChemElementName::notdefined,oxide]
];

NumberOxygens[oxide_]:=If[MemberQ[OxideCoefficients,{oxide,__}],
OxideCoefficients[[Position[OxideCoefficients,{oxide,__}]//First,4]]//First,
ErrorMessage[ChemElementName::notdefined,oxide]
];

ChemElementName::notdefined="The oxide `1` is not defined. Please register it first using the RegisterOxide function.";

ChemElementName::usage="ChemElementName[oxide] returns the element string for the registered chemical oxide compound 'oxide'.";
ChemElementName[oxide_]:=If[MemberQ[OxideCoefficients,{oxide,__}],
OxideCoefficients[[Position[OxideCoefficients,{oxide,__}]//First,2]]//First,
ErrorMessage[ChemElementName::notdefined,oxide]
];


(* ::Input::Initialization:: *)
OxideName::notdefined="The element `1` has no defined oxide. Please register it first using the RegisterOxide function.";

OxideName::usage="OxideName[element] returns the oxide string for the registered chemical element 'element'.";
OxideName[element_]:=If[MemberQ[OxideCoefficients,{_,element,__}],
OxideCoefficients[[Position[OxideCoefficients,{_,element,__}]//First,1]]//First,
ErrorMessage[OxideName::notdefined,element]
];


(* ::Input::Initialization:: *)
Options[MinFormulaCations]={FromMolarData->False};


(* ::Input::Initialization:: *)
MinFormulaCations::usage="MinFormulaCations[datatable,i] converts the compositional data (given in terms of oxides) in a DataTable to coefficients for a chemical mineral formula based on i oxygen atoms in the formula.";
MinFormulaCations[dt_DataTable,oxygens_,OptionsPattern[]]:=Module[
{
MolarData,
OxygenData
},
MolarData=If[OptionValue[FromMolarData],dt,MassToMoles[dt]];
If[FailureQ[MolarData],Print[MolarData];Abort[];];

OxygenData=MolarData[All,Association@KeyValueMap[#1->(NumberOxygens[#1]*#2)&,#]&];
If[FailureQ[OxygenData],Print[OxygenData];Abort[];];

OxygenData=OxygenData[All,(#/Total[#]*oxygens)&];

OxygenData[All,Association@KeyValueMap[ChemElementName[#1]->(#2*NumberCations[#1]/NumberOxygens[#1])&,#]&]
];

MinFormulaCations[assoc_Association,oxygens_,opts:OptionsPattern[]]:=If[Depth@DVMeans@assoc==2,
First@Normal@MinFormulaCations[DataTable[<|"1"->assoc|>],oxygens,opts],
Normal@MinFormulaCations[DataTable[assoc],oxygens,opts]
];


(* ::Input::Initialization:: *)
End[];


(* ::Input::Initialization:: *)
Protect[Evaluate[$Context<>"*"]];
EndPackage[];
