using System.Numerics; using Skeleton.Algebra.Extensions; using Skeleton.Algebra.ScalarFieldStructure; using Skeleton.DataStructure; using Skeleton.DataStructure.Link; using Skeleton.DataStructure.Packs; using Skeleton.Utils; using Skeleton.Utils.Helpers; using Skeleton.Utils.InverseSampling; using VirtualChemistry.Chemistry.Bonds.Implements; using VirtualChemistry.Chemistry.Compounds.Implements; using VirtualChemistry.Chemistry.Mixtures.Resolver; using VirtualChemistry.Constants; using VirtualChemistry.DataStructure.Packs; namespace VirtualChemistry.Chemistry.Mixtures.Implements; /// /// mixture of many well mixed compounds that can not be seperated by psy methods /// public class HomogeneousMixture { /// /// Constructor by default /// public HomogeneousMixture() { LogStateMatrix = new CacheItem(x => Compounds.Select(compound => compound.LogStateMatrix.GetFrom(x) * compound.Concentration) .DefaultIfEmpty(LieSU3.Zero) .SpLieSum() ); StateMatrix = new(x => LogStateMatrix.GetFrom(x).Exp()); CompressionElasticity = new(x => { DiagR3 spectrum = new (0d, 2d * Math.PI, 4d * Math.PI); return (LogStateMatrix.GetFrom(x).CayleyNegative() * StateMatrix.GetFrom(x)) .ConjugateOn(spectrum) .Rayleigh(LogStateMatrix.GetFrom(x).ColumnAverage); } ); CompressionBias = new(x => { DiagR3 w1 = new (1d / 11d, 7d / 11d, 10d / 11d); DiagR3 w2 = new (2d / 17d, 8d / 17d, 16d / 17d); double p1 = StateMatrix.GetFrom(x) .ConjugateOn(w1) .Rayleigh(StateMatrix.GetFrom(x).Hermitian().ColumnAverage); double p2 = StateMatrix.GetFrom(x) .Hermitian() .ConjugateOn(w2) .Rayleigh(StateMatrix.GetFrom(x).ColumnAverageBivariant); double x1 = InverseSampling.StandardNormal(p1) / 4d - 2d * Math.PI; double x2 = InverseSampling.StandardNormal(p2) / 3d + 2d * Math.PI; return Math.Atan(Math.Abs(x1) > Math.Abs(x2) ? x1 : x2) * 2d; } ); FreeDensity = new(x => { double avg = (Math.Exp(-4d * Math.PI) + Math.Exp(Math.PI)) / 2d; DiagR3 spectrum = new DiagR3(Math.Exp(-4d * Math.PI), avg, Math.Exp(Math.PI)); double tempMod = Math.Exp(-Phase); return tempMod * LogStateMatrix.GetFrom(x) .CayleyPositive() .ConjugateOn(spectrum) .Rayleigh(StateMatrix.GetFrom(x).ColumnAverageBivariant); } ); HeatConductivity = new(x => StateMatrix.GetFrom(x).ConjugateOn(ChemistryConstant.EnergyConductivitySpectrum) .Rayleigh(LogStateMatrix.GetFrom(x) * StateMatrix.GetFrom(x).ColumnAverageBivariant) ); ResetAllCache(); } /// /// singleton used to represent no mixture /// public static readonly HomogeneousMixture Null = new(); private IEnumerable ConnectedIndex() { HashSet bs = new(); foreach (Compound c in Compounds) { if(c.Amount.IsEqualApprox(0)) continue; foreach (BaseBond b in c.Bonds) { if ( b.Connected && b.Energy > b.AntiBondingEnergy && b.ConnectedBond.Energy > b.ConnectedBond.AntiBondingEnergy && !bs.Contains(b.ConnectedBond) ) { bs.Add(b); } } } return bs; //Compounds.Where(c => !(0.5 * c.Amount).IsEqualApprox(0)).SelectMany(x => x.ConnectedIndex()); } public void RPC() { int s1 = 0; int s2 = 0; int s3 = 0; int s4 = 0; foreach (Compound c in Compounds) { foreach (BaseBond b in c.Bonds) { switch (b.BondingGroup) { case 0: s1++; break; case 1: s2++; break; case 2: s3++; break; default: s4++; break; } } } Console.WriteLine($"{s1} ---- {s2} ---- {s3} --- {s4}"); } private BondGroups ReactionGroup() { HashSet group1 = new(); HashSet group2 = new(); HashSet group3 = new(); HashSet group4 = new(); foreach (Compound c in Compounds) { if ((c.Amount * 0.125).IsEqualApprox(0) || c.Atoms.Count > 15) continue; foreach(BaseBond b in c.Bonds) { if(b.Connected || !(b.Energy > b.BondingEnergy)) continue; switch (b.BondingGroup) { case 0: group1.Add(b); break; case 1: group2.Add(b); break; case 2: group3.Add(b); break; case 3: group4.Add(b); break; } } } return new(group1, group2, group3, group4); } /// /// elasticity range: 0, 4pi /// elasticity controls the speed k drops from e^pi to e^-4pi /// elasticity -> 0; k -> constant /// b -> 4pi; k -> e^pi /// b -> -4pi; k -> e^-4pi /// public CacheItem CompressionElasticity { get; } /// /// bias range: -4pi, 4pi /// bias controls the temp of triple point /// higher b -> higher melting point and higher boiling point /// public CacheItem CompressionBias { get; } /// /// phase -> 1 implies material is more like gas /// phase -> -1 implies material is more like solid /// /// public double Phase => Compounds.Sum(c => c.Concentration * c.Phase); /// /// stiffness range: e^-4pi, e^pi /// higher stiffness -> harder to compress -> solid like /// lower stiffness -> easier to compress -> gas like /// Stiffness K, Density D, Free Density D0, Temperature T /// P = K(1/D - 1/D0) /// /// Temperature range: -pi, pi /// public double CompressionStiffness => Math.Exp(Math.PI / 2d * (5d * Phase - 3d)); /// /// e^-4pi ... e^pi /// public CacheItem FreeDensity { get; } /// /// volume that under 0 compression /// public double FreeVolume => Amount / FreeDensity.Get; /// /// P = K (1/D - 1/D0) /// public double Pressure => (Volume - FreeVolume) * CompressionStiffness; /// /// Volume of the mixture /// public double Volume { get; set; } /// /// Heat transfer ability /// public CacheItem HeatConductivity { get; } /// /// energy / amount = temperature /// public double Temperature { get => Energy / Amount; set => Energy = value * Amount; } /// /// combust rate /// public double CombustRate { get { H3 w = StateMatrix.Get!.ConjugateOn(new DiagR3(0, Math.PI, 2 * Math.PI)); return w.Rayleigh( LogStateMatrix.Get!.ColumnAverageBivariant + StateMatrix.Get.Hermitian().ColumnAverageBivariant ); } } /// /// total Energy the mixture holds /// public double Energy { get => Compounds.Sum(c => c.Energy); set { foreach (Compound c in Compounds) c.Energy = value * c.Concentration; } } /// /// reset all cache /// public void ResetAllCache() { LayerCache = LinkNode.Null; } /// /// the heterogeneous mixture that holds this homogeneous mixture /// public HeterogeneousMixture HeterogeneousMixture { get; set; } = HeterogeneousMixture.Null; /// /// Compounds of this mixture /// public HashSet Compounds { get; set; } = new HashSet(); /// /// Compounds whose ratio is greater than 1% /// public IEnumerable MainCompounds => Compounds.Where(c => c.Concentration > 0.001d); private LinkNode LayerCache { get; set; } = LinkNode.Null; /// /// /// public LinkNode Layer { get => LayerCache == LinkNode.Null ? HeterogeneousMixture.LayerOrder.Find(this) : LayerCache; set => LayerCache = value; } /// /// State matrix of this mixture /// public CacheItem StateMatrix { get; } /// /// Log of the state matrix /// public CacheItem LogStateMatrix { get; } /// /// Split the homogeneous mixture into another heterogeneous mixture by ratio or amount; /// /// The amount or ratio /// another heterogeneous mixture /// determine if the amount variable is ratio or amount /// public HomogeneousMixture Split(double amount, HeterogeneousMixture into, bool byRatio = false) { double ebs = Energy; double vbs = Volume; double ratio = byRatio ? amount : amount / Amount; HomogeneousMixture res = new HomogeneousMixture(); foreach (Compound c in Compounds) { Compound split = c.Split(amount, byRatio); if(split != Compound.Null) res.AddCompound(split); } res.Energy = ebs*ratio; res.Volume = vbs*ratio; Energy = ebs * (1 - ratio); Volume = vbs * (1 - ratio); into.AddLayer(res); return res; } /// /// Remove a compound from the homogeneous mixture /// /// public void RemoveCompound(Compound compound) { double deltaV = compound.Volume; Compounds.Remove(compound); compound.HomogeneousMixture = Null; Volume -= deltaV; } /// /// Add compound into the homogeneous mixture /// /// compound to add /// /// add without combine to isomorphic compound public void AddCompound(Compound compound, bool resetCache = true, bool skipCombine=false) { if (compound.HomogeneousMixture == this) return; bool added = false; if(!skipCombine) { foreach (Compound sCompound in Compounds.Where(x => x.Expression == compound.Expression)) { if (sCompound.IsometricTo(compound)) { sCompound.Amount += compound.Amount; compound.Amount = 0d; sCompound.Energy += compound.Energy; added = true; break; } } } compound.HomogeneousMixture = added ? Null : this; if (!added) Compounds.Add(compound); if(resetCache) ResetAllCache(); } /// /// Amount of this mixture /// public double Amount { get => Compounds .Select(c => c.Amount) .DefaultIfEmpty(0) .Sum(); set { Dictionary res = new Dictionary(); foreach (Compound c in Compounds) res[c] = value * c.Concentration; foreach (Compound c in Compounds) c.Amount = res[c]; } } /// /// ratio of this mixture in the heterogeneous mixture /// public double Ratio => Amount / (HeterogeneousMixture == HeterogeneousMixture.Null ? Amount : HeterogeneousMixture.Amount); /// /// density of the mixture, amount of molecular per volume /// public double Density => Amount / Volume; /// /// save mixture into string /// /// public string Dump() => $"`ENV?ENG{Energy.ExactDoubleString()}?V{Volume.ExactDoubleString()}`%ENV`COMPS{String.Join(HomogeneousMixtureResolver.CompoundsSplit, Compounds.Select(c => c.Dump()))}`%COMPS"; /// /// update volume /// public void VolumeUpdate() { double sVolume = Volume + HeterogeneousMixture.ForceFromContainer / CompressionStiffness; Volume = Math.Max(sVolume, 1E-6); } /// /// exchange energy to neighbor mixtures /// public void HeatExchange() { if (Layer != Layer.Parent.First) { HomogeneousMixture mPrevious = Layer.Previous.Value; double balanceTemp = (Energy + mPrevious.Energy) / (Amount + mPrevious.Amount); double dTemp = balanceTemp - Temperature; double conductivity = Math.Sqrt(HeatConductivity.Get * mPrevious.HeatConductivity.Get); double transferTemp = dTemp * conductivity; Temperature += transferTemp; mPrevious.Temperature -= transferTemp; } if (Layer != Layer.Parent.Last) { HomogeneousMixture mNext = Layer.Next.Value; double balanceTemp = (Energy + mNext.Energy) / (Amount + mNext.Amount); double dTemp = balanceTemp - Temperature; double conductivity = Math.Sqrt(HeatConductivity.Get * mNext.HeatConductivity.Get); double transferTemp = dTemp * conductivity; Temperature += transferTemp; mNext.Temperature -= transferTemp; } double envBalanceTemp = HeterogeneousMixture.Container.EnvironmentTemperature; double envDTemp = envBalanceTemp - Temperature; double envConductivity = HeatConductivity.Get; Temperature += envDTemp * envConductivity; } private void ReactConnecting(HashSet g1, HashSet g2) { double maxCd = -1; BaseBond xb1 = BaseBond.Null; BaseBond xb2 = BaseBond.Null; foreach (BaseBond b1 in g1) { foreach (BaseBond b2 in g2) { H3 cd = new(Complex.ImaginaryOne / 2 * (b1.LogState.Get + b2.LogState.Get)); double cdX = cd.Rayleigh((b1.LogState.Get + b2.LogState.Get).ColumnAverage); if (cdX > maxCd) { maxCd = cdX; xb1 = b1; xb2 = b2; } } } if (xb1 == BaseBond.Null) return; if (xb1 == xb2) { PlainPack2 s = xb1.Compound.SplitWithBondMap(0.5, xb2, true); xb2 = s.Item2; } if (xb1.Compound.Amount.IsSignificantlyGreaterThen(xb2.Compound.Amount)) { double amountDiff = xb1.Compound.Amount - xb2.Compound.Amount; Compound oth = xb1.Compound.Split(amountDiff); xb1.HomogeneousMixture.AddCompound(oth); } if (xb1.Compound.Amount.IsSignificantlyLessThen(xb2.Compound.Amount)) { double amountDiff = xb2.Compound.Amount - xb1.Compound.Amount; Compound oth = xb2.Compound.Split(amountDiff); xb2.HomogeneousMixture.AddCompound(oth); } xb1.Connect(xb2); } private int ReactionType() { Complex d = StateMatrix.Get.Trace(); return d.Phase switch { > 7 * Math.PI / 4 or < Math.PI / 4 => 0, > Math.PI / 4 and < 3 * Math.PI / 4 => 1, > 3 * Math.PI / 4 and < 5 * Math.PI / 4 => 2, _ => 3 }; } /// /// react within the homogeneous mixture /// public void React() { BaseBond[] connected = ConnectedIndex().ToArray(); foreach (BaseBond bond in connected) { if (bond.Compound.Amount.IsEqualApprox(0)) continue; if ( bond.Energy > bond.AntiBondingEnergy && bond.ConnectedBond.Energy > bond.ConnectedBond.AntiBondingEnergy && bond.DisconnectCondition && bond.ConnectedBond.DisconnectCondition ) { bond.Disconnect(); } } for (int i = 1; i <= 3; i++) { BondGroups bg = ReactionGroup(); int type = ReactionType(); if(type == 0) { ReactConnecting(bg.Group1, bg.Group3); ReactConnecting(bg.Group2, bg.Group4); } else if (type == 1) { ReactConnecting(bg.Group1, bg.Group1); ReactConnecting(bg.Group3, bg.Group3); } else if (type == 2) { ReactConnecting(bg.Group2, bg.Group2); ReactConnecting(bg.Group3, bg.Group3); } else { ReactConnecting(bg.Group1, bg.Group4); ReactConnecting(bg.Group2, bg.Group3); } } } /// /// higher level mixture can resolve lower level mixture /// public double ResolveLevel => Compounds.Sum(c => c.Concentration * c.ResolveLevel); /// /// eigen resolvability of other mixture /// /// /// public double EigenResolvability(HomogeneousMixture oth) { LieSU3 s = oth.LogStateMatrix.Get ^ LogStateMatrix.Get; C3 a = new(1, 0.5, 1); C3 b = new(0.5, 1, 0.5); Complex w = a * s * b; ComplexFieldStructure.Structure.Fix(w); double res = w.Phase; if (res > Math.PI) res = 2 * Math.PI - res; return res; } /// /// eigen resolvability from -pi to pi /// /// /// public double EigenResolvability(Compound c) { LieSU3 s = c.LogStateMatrix.Get ^ LogStateMatrix.Get; C3 a = new(1, 0.5, 1); C3 b = new(0.5, 1, 0.5); Complex w = a * s * b; w = ComplexFieldStructure.Structure.Fix(w); double res = w.Phase; if (res > Math.PI) res = 2 * Math.PI - res; return res; } /// /// calculate resolvability from eigen resolvability /// /// /// public double Resolvability(double eigenRes) { double res = -Math.Tan((Math.Sin(eigenRes) * Math.PI - Math.PI) / 2); if (Math.Abs(res) < 1E-6) return res; return res; } /// /// ability to resolve other compound /// /// /// public double Resolvability(Compound c) { LieSU3 s = c.LogStateMatrix.Get ^ LogStateMatrix.Get; C3 a = new(1, 0.5, 1); C3 b = new(0.5, 1, 0.5); Complex w = a * s * b; w = ComplexFieldStructure.Structure.Fix(w); double res = -Math.Tan((Math.Sin(w.Phase) * Math.PI - Math.PI) / 2); if (Math.Abs(res) < 1E-6) return 0; return res; } /// /// a tensor of order 3 abstracted from this mixture /// public C3 Ita => Compounds .Select(c => c.Concentration * c.StateMatrix.Get.ColumnAverageBivariant) .Aggregate((a, b) => a + b); /// /// Red component /// public Byte ColorRed => (Byte) ( StateMatrix.Get .ConjugateOn(ChemistryConstant.OpticalFilter.RedDefinite) .Rayleigh(Ita) * 255 ); /// /// Green component /// public Byte ColorGreen => (Byte) ( StateMatrix.Get .ConjugateOn(ChemistryConstant.OpticalFilter.GreenDefinite) .Rayleigh(Ita) * 255 ); /// /// Blue component /// public Byte ColorBlue => (Byte) ( StateMatrix.Get .ConjugateOn(ChemistryConstant.OpticalFilter.BlueDefinite) .Rayleigh(Ita) * 255 ); /// /// Transparent component /// public Byte ColorTransparent => (Byte) ( StateMatrix.Get .ConjugateOn(ChemistryConstant.OpticalFilter.OpacityDefinite) .Rayleigh(Ita) * 255 ); public double OsmoticLevel => StateMatrix.Get .ConjugateOn(new DiagR3(Phase, Math.PI, -Phase)) .Rayleigh(LogStateMatrix.Get.ColumnAverage); /// /// Should not be used by any methods except ResolveNext and ResolvePrev /// private void PureResolve(HomogeneousMixture hOther) { HashSet< (Compound, double)> toAbsorb = new (); if (hOther.OsmoticLevel > OsmoticLevel) return; foreach (Compound c in hOther.Compounds) { double eigenRes = EigenResolvability(c); double maxRes = Resolvability(eigenRes); double oRes = hOther.Resolvability(c); if(oRes < 0 || maxRes < oRes) continue; if (eigenRes.IsEqualApprox(0)) { toAbsorb.Add((c, c.Amount)); continue; } double maxResAmount = Amount; foreach (Compound ct in Compounds) if (ct.IsometricTo(c)) maxResAmount -= ct.Amount; maxResAmount *= maxRes; if(maxResAmount < 0) continue; double splitAmount = Math.Min(maxResAmount, c.Amount); if(splitAmount.IsEqualApprox(0)) continue; toAbsorb.Add((c, splitAmount)); } foreach ((Compound c, double a) in toAbsorb) { Compound sp = c.Split(a); double v = sp.Volume; hOther.Volume -= v; Volume += v; /*if (c.Amount.IsEqualApprox(0)) hOther.RemoveCompound(c);*/ AddCompound(sp); } if(hOther.Amount.IsEqualApprox(0)) HeterogeneousMixture.RemoveLayer(hOther); } private void ResolveNext() { if (HeterogeneousMixture == HeterogeneousMixture.Null || Layer.Next.IsTail) return; PureResolve(Layer.Next.Value); } private void ResolvePrevious() { if (HeterogeneousMixture == HeterogeneousMixture.Null || Layer.Previous.IsHead) return; PureResolve(Layer.Previous.Value); } private bool Resolve() { ResolveNext(); ResolvePrevious(); return false; } private bool Precipitate() { HashSet<(Compound, double)> toDeposit = new(); foreach (Compound c in Compounds) { double maxRes = Resolvability(c); if (maxRes < 0 || maxRes > c.Concentration) continue; double maxResAmount = maxRes * (Amount - c.Amount); if(maxResAmount.IsEqualApprox(0)) continue; double aDiff = c.Amount - maxResAmount; if (aDiff.IsSignificantlyGreaterThen(0) ) toDeposit.Add((c, aDiff)); } foreach ((Compound c, double a) in toDeposit) { Compound oth = c.Split(a); if (oth.FreeDensity.Get > FreeDensity.Get) { if (Layer.Next.IsEnding) { HomogeneousMixture m = HeterogeneousMixture.AddLayer(); m.Layer.MoveAfter(Layer); m.AddCompound(oth); m.Volume = m.FreeVolume; return true; } Layer.Next.Value.AddCompound(oth); } else { if (Layer.Previous.IsEnding) { HomogeneousMixture m = HeterogeneousMixture.AddLayer(); m.Layer.MoveBefore(Layer); m.AddCompound(oth); m.Volume = m.FreeVolume; return true; } Layer.Previous.Value.AddCompound(oth); } } return false; } /// /// /// /// if any new layer created or deleted public bool PrecipitateAndResolve() { bool a = Resolve(); bool b = Precipitate(); return a || b; } /// /// combine identical compounds in the mixture /// public void Degenerate() { HashSet toRemove = new(); foreach (IGrouping g in Compounds.GroupBy(c => c.Expression)) { HashSet classified = new(); foreach (Compound c in g) { classified.Add(c); foreach (Compound cx in g.Where(x => !classified.Contains(x))) { if (!c.IsometricTo(cx)) continue; classified.Add(cx); c.Amount += cx.Amount; cx.Amount = 0; toRemove.Add(cx); } } } foreach (Compound c in toRemove) { c.HomogeneousMixture = Null; Compounds.Remove(c); } } }