From 6bd22bc9b12c2b8d49513418d8bdfe295584235a Mon Sep 17 00:00:00 2001 From: Andy Black Date: Wed, 27 Aug 2025 10:02:25 -0700 Subject: [PATCH 1/2] LT-22190 Use BitArray for large symbolic feature value sets --- .../FeatureModel/ISymbolicFeatureValue.cs | 50 ++++ .../FeatureModel/SymbolicFeature.cs | 19 +- .../FeatureModel/SymbolicFeatureValue.cs | 210 +++++++++---- .../SymbolicFeatureValueBitArray.cs | 275 ++++++++++++++++++ .../FeatureModel/SymbolicFeatureValueUlong.cs | 139 +++++++++ .../FeatureModel/FeatureStructTests.cs | 145 ++++++++- 6 files changed, 774 insertions(+), 64 deletions(-) create mode 100644 src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs create mode 100644 src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs create mode 100644 src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs diff --git a/src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs b/src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs new file mode 100644 index 000000000..3bdf4b57a --- /dev/null +++ b/src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace SIL.Machine.FeatureModel +{ + internal interface ISymbolicFeatureValue + { + void ExceptWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ); + bool Get(SymbolicFeatureValue sfv, FeatureSymbol symbol); + int GetValuesHashCode(SymbolicFeatureValue sfv, int code); + void IntersectWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ); + bool IsSatisfiable(SymbolicFeatureValue sfv); + bool IsSupersetOf( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ); + bool IsUninstantiated(SymbolicFeatureValue sfv, SymbolicFeature feature); + bool Overlaps( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ); + void Set(SymbolicFeatureValue sfv, IEnumerable symbols); + void SetFirst(SymbolicFeatureValue sfv, SymbolicFeature feature); + void UnionWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ); + bool ValueEquals(SymbolicFeatureValue sfv, SymbolicFeatureValue otherSFv); + } +} diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeature.cs b/src/SIL.Machine/FeatureModel/SymbolicFeature.cs index 56361aded..e627253e1 100644 --- a/src/SIL.Machine/FeatureModel/SymbolicFeature.cs +++ b/src/SIL.Machine/FeatureModel/SymbolicFeature.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using SIL.ObjectModel; @@ -7,6 +8,7 @@ public class SymbolicFeature : Feature { private readonly PossibleSymbolCollection _possibleSymbols; private readonly ulong _mask; + private readonly BitArray _maskBA = new BitArray(sizeof(ulong) * 8, false); public SymbolicFeature(string id, params FeatureSymbol[] possibleSymbols) : this(id, (IEnumerable)possibleSymbols) { } @@ -21,7 +23,16 @@ public SymbolicFeature(string id, IEnumerable possibleSymbols) symbol.Feature = this; symbol.Index = i++; } - _mask = (1UL << _possibleSymbols.Count) - 1UL; + + int symbolCount = _possibleSymbols.Count; + if (symbolCount > SymbolicFeatureValue.NeedToUseBitArray) + { + _maskBA = new BitArray(symbolCount, true); + } + else + { + _mask = (1UL << symbolCount) - 1UL; + } } /// @@ -38,9 +49,13 @@ public string DefaultSymbolID set { DefaultValue = new SymbolicFeatureValue(_possibleSymbols[value]); } } - internal ulong Mask + internal ulong MaskUlong { get { return _mask; } } + internal BitArray MaskBitArray + { + get { return _maskBA; } + } } } diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs index 4f982c5d5..c2cf99e04 100644 --- a/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs +++ b/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -16,15 +17,26 @@ public static implicit operator SymbolicFeatureValue(FeatureSymbol symbol) public static explicit operator FeatureSymbol(SymbolicFeatureValue sfv) { - return sfv._first; + return sfv.First; } + private SymbolicFeatureValueUlong _sfvUlong; + private SymbolicFeatureValueBitArray _sfvBitArray; private readonly SymbolicFeature _feature; - private ulong _flags; + private ulong _flagsUlong; private FeatureSymbol _first; + private BitArray _flagsBitArray = null; + private int _bitArraySize = 0; + + // this can be set to 0 for testing the BitArray code + public static int NeedToUseBitArray { get; set; } = sizeof(ulong) * 8; + + // To test using the BitArray method, comment the preceding line and uncomment the following line + //public static int NeedToUseBitArray { get; set; } = 0; public SymbolicFeatureValue(SymbolicFeature feature) { + MakeValueItemToUse(feature.PossibleSymbols.Count); _feature = feature; } @@ -34,20 +46,23 @@ public SymbolicFeatureValue(IEnumerable values) if (symbols.Length == 0) throw new ArgumentException("values cannot be empty", "values"); _feature = symbols[0].Feature; - _first = symbols[0]; + MakeValueItemToUse(_feature.PossibleSymbols.Count); + First = symbols[0]; Set(symbols); } public SymbolicFeatureValue(FeatureSymbol value) { _feature = value.Feature; - _first = value; + MakeValueItemToUse(_feature.PossibleSymbols.Count); + First = value; Set(value.ToEnumerable()); } public SymbolicFeatureValue(SymbolicFeature feature, string varName, bool agree) : base(varName, agree) { + MakeValueItemToUse(feature.PossibleSymbols.Count); _feature = feature; } @@ -55,26 +70,49 @@ private SymbolicFeatureValue(SymbolicFeatureValue sfv) : base(sfv) { _feature = sfv._feature; - _first = sfv._first; - _flags = sfv._flags; + MakeValueItemToUse(_feature.PossibleSymbols.Count); + First = sfv.First; + FlagsUlong = sfv.FlagsUlong; + if (sfv.FlagsBitArray != null) + FlagsBitArray = new BitArray(sfv.FlagsBitArray); } private SymbolicFeatureValue(SymbolicFeature feature, ulong flags) { + MakeValueItemToUse(feature.PossibleSymbols.Count); _feature = feature; - _flags = flags; + FlagsUlong = flags; SetFirst(); } - private void Set(IEnumerable symbols) + public SymbolicFeatureValue(SymbolicFeature feature, BitArray notFlagsAndFeatureMask) + : this(feature) + { + FlagsBitArray = new BitArray(notFlagsAndFeatureMask); + } + + private void MakeValueItemToUse(int size) { - foreach (FeatureSymbol symbol in symbols) + BitArraySize = size; + if (BitArraySize <= NeedToUseBitArray) { - ulong mask = 1UL << symbol.Index; - _flags |= mask; + SfvUlong = new SymbolicFeatureValueUlong(); + } + else + { + _flagsBitArray = new BitArray(BitArraySize, false); + SfvBitArray = new SymbolicFeatureValueBitArray(); } } + private void Set(IEnumerable symbols) + { + if (SfvUlong != null) + SfvUlong.Set(this, symbols); + else + SfvBitArray.Set(this, symbols); + } + public IEnumerable Values { get { return _feature.PossibleSymbols.Where(Get); } @@ -92,12 +130,18 @@ public bool Overlaps(SymbolicFeatureValue other, bool notOther = false) private void SetFirst() { - _first = _flags == 0 ? null : _feature.PossibleSymbols.First(Get); + if (SfvUlong != null) + SfvUlong.SetFirst(this, _feature); + else + SfvBitArray.SetFirst(this, _feature); } - private bool Get(FeatureSymbol symbol) + public bool Get(FeatureSymbol symbol) { - return (_flags & (1UL << symbol.Index)) != 0; + if (SfvUlong != null) + return SfvUlong.Get(this, symbol); + else + return SfvBitArray.Get(this, symbol); } public SymbolicFeature Feature @@ -107,12 +151,62 @@ public SymbolicFeature Feature protected override bool IsSatisfiable { - get { return base.IsSatisfiable || _flags != 0; } + get + { + bool sfvValue = false; + if (SfvUlong != null) + sfvValue = SfvUlong.IsSatisfiable(this); + else + sfvValue = SfvBitArray.IsSatisfiable(this); + return base.IsSatisfiable || sfvValue; + } } protected override bool IsUninstantiated { - get { return base.IsUninstantiated && _flags == _feature.Mask; } + get + { + bool sfvValue = false; + if (SfvUlong != null) + sfvValue = SfvUlong.IsUninstantiated(this, _feature); + else + sfvValue = SfvBitArray.IsUninstantiated(this, _feature); + return base.IsUninstantiated && sfvValue; + } + } + + public BitArray FlagsBitArray + { + get => _flagsBitArray; + set => _flagsBitArray = value; + } + + public ulong FlagsUlong + { + get => _flagsUlong; + set => _flagsUlong = value; + } + public FeatureSymbol First + { + get => _first; + set => _first = value; + } + + internal SymbolicFeatureValueUlong SfvUlong + { + get => _sfvUlong; + set => _sfvUlong = value; + } + + internal SymbolicFeatureValueBitArray SfvBitArray + { + get => _sfvBitArray; + set => _sfvBitArray = value; + } + public int BitArraySize + { + get => _bitArraySize; + set => _bitArraySize = value; } protected override bool IsSupersetOf(bool not, SimpleFeatureValue other, bool notOther) @@ -120,14 +214,10 @@ protected override bool IsSupersetOf(bool not, SimpleFeatureValue other, bool no if (!(other is SymbolicFeatureValue otherSfv)) return false; - if (!not && !notOther) - return (_flags & otherSfv._flags) == otherSfv._flags; - if (!not) - return (_flags & (~otherSfv._flags & _feature.Mask)) == (~otherSfv._flags & _feature.Mask); - if (!notOther) - return ((~_flags & _feature.Mask) & otherSfv._flags) == otherSfv._flags; - return ((~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask)) - == (~otherSfv._flags & _feature.Mask); + if (SfvUlong != null) + return SfvUlong.IsSupersetOf(not, this, otherSfv, notOther, _feature); + else + return SfvBitArray.IsSupersetOf(not, this, otherSfv, notOther, _feature); } protected override bool Overlaps(bool not, SimpleFeatureValue other, bool notOther) @@ -135,13 +225,10 @@ protected override bool Overlaps(bool not, SimpleFeatureValue other, bool notOth if (!(other is SymbolicFeatureValue otherSfv)) return false; - if (!not && !notOther) - return (_flags & otherSfv._flags) != 0; - if (!not) - return (_flags & (~otherSfv._flags & _feature.Mask)) != 0; - if (!notOther) - return ((~_flags & _feature.Mask) & otherSfv._flags) != 0; - return ((~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask)) != 0; + if (SfvUlong != null) + return SfvUlong.Overlaps(not, this, otherSfv, notOther, _feature); + else + return SfvBitArray.Overlaps(not, this, otherSfv, notOther, _feature); } protected override void IntersectWith(bool not, SimpleFeatureValue other, bool notOther) @@ -149,14 +236,10 @@ protected override void IntersectWith(bool not, SimpleFeatureValue other, bool n if (!(other is SymbolicFeatureValue otherSfv)) return; - if (!not && !notOther) - _flags = _flags & otherSfv._flags; - else if (!not) - _flags = _flags & (~otherSfv._flags & _feature.Mask); - else if (!notOther) - _flags = (~_flags & _feature.Mask) & otherSfv._flags; + if (SfvUlong != null) + SfvUlong.IntersectWith(not, this, otherSfv, notOther, _feature); else - _flags = (~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask); + SfvBitArray.IntersectWith(not, this, otherSfv, notOther, _feature); SetFirst(); } @@ -165,14 +248,11 @@ protected override void UnionWith(bool not, SimpleFeatureValue other, bool notOt if (!(other is SymbolicFeatureValue otherSfv)) return; - if (!not && !notOther) - _flags = _flags | otherSfv._flags; - else if (!not) - _flags = _flags | (~otherSfv._flags & _feature.Mask); - else if (!notOther) - _flags = (~_flags & _feature.Mask) | otherSfv._flags; + if (SfvUlong != null) + SfvUlong.UnionWith(not, this, otherSfv, notOther, _feature); else - _flags = (~_flags & _feature.Mask) | (~otherSfv._flags & _feature.Mask); + SfvBitArray.UnionWith(not, this, otherSfv, notOther, _feature); + SetFirst(); } @@ -181,14 +261,10 @@ protected override void ExceptWith(bool not, SimpleFeatureValue other, bool notO if (!(other is SymbolicFeatureValue otherSfv)) return; - if (!not && !notOther) - _flags = _flags & (~otherSfv._flags & _feature.Mask); - else if (!not) - _flags = _flags & otherSfv._flags; - else if (!notOther) - _flags = (~_flags & _feature.Mask) & (~otherSfv._flags & _feature.Mask); + if (SfvUlong != null) + SfvUlong.ExceptWith(not, this, otherSfv, notOther, _feature); else - _flags = (~_flags & _feature.Mask) & otherSfv._flags; + SfvBitArray.ExceptWith(not, this, otherSfv, notOther, _feature); } protected override SimpleFeatureValue CloneImpl() @@ -198,9 +274,21 @@ protected override SimpleFeatureValue CloneImpl() public override SimpleFeatureValue Negation() { - return IsVariable - ? new SymbolicFeatureValue(_feature, VariableName, !Agree) - : new SymbolicFeatureValue(_feature, (~_flags & _feature.Mask)); + if (SfvUlong != null) + { + return IsVariable + ? new SymbolicFeatureValue(_feature, VariableName, !Agree) + : new SymbolicFeatureValue(_feature, (~FlagsUlong & _feature.MaskUlong)); + } + else + { + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(_flagsBitArray); + BitArray notFlagsAndFeatureMask = flags.Not().And(_feature.MaskBitArray); + return IsVariable + ? new SymbolicFeatureValue(_feature, VariableName, !Agree) + : new SymbolicFeatureValue(_feature, notFlagsAndFeatureMask); + } } public override bool ValueEquals(SimpleFeatureValue other) @@ -213,13 +301,21 @@ public bool ValueEquals(SymbolicFeatureValue other) if (other == null) return false; - return base.ValueEquals(other) && _flags == other._flags; + bool sfvValue = false; + if (SfvUlong != null) + sfvValue = SfvUlong.ValueEquals(this, other); + else + sfvValue = SfvBitArray.ValueEquals(this, other); + return base.ValueEquals(other) && sfvValue; } protected override int GetValuesHashCode() { int code = base.GetValuesHashCode(); - return code * 31 + _flags.GetHashCode(); + if (SfvUlong != null) + return SfvUlong.GetValuesHashCode(this, code); + else + return SfvBitArray.GetValuesHashCode(this, code); } public new SymbolicFeatureValue Clone() diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs new file mode 100644 index 000000000..473aff013 --- /dev/null +++ b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace SIL.Machine.FeatureModel +{ + internal class SymbolicFeatureValueBitArray : ISymbolicFeatureValue + { + public void ExceptWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray notOtherFlagsAndFeatureMask = new BitArray( + new BitArray(otherSfv.FlagsBitArray).Not().And(feature.MaskBitArray) + ); + if (!not && !notOther) + { + sfv.FlagsBitArray.And(notOtherFlagsAndFeatureMask); + } + else if (!not) + { + sfv.FlagsBitArray.And(otherSfv.FlagsBitArray); + } + else + { + BitArray notFlagsAndFeatureMask = new BitArray( + new BitArray(sfv.FlagsBitArray).Not().And(feature.MaskBitArray) + ); + if (!notOther) + { + sfv.FlagsBitArray = notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask); + } + else + { + sfv.FlagsBitArray = notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray); + } + } + } + + public bool Get(SymbolicFeatureValue sfv, FeatureSymbol symbol) + { + return sfv.FlagsBitArray.Get(symbol.Index); + } + + public int GetValuesHashCode(SymbolicFeatureValue sfv, int code) + { + return code * 31 + GetHashCode(sfv.FlagsBitArray); + } + + public int GetHashCode(BitArray array) + { + int hash = 0; + foreach (bool value in array) + { + hash ^= (value ? 2 : 1); + } + return hash; + } + + public bool IsSatisfiable(SymbolicFeatureValue sfv) + { + return HasAnySet(sfv.FlagsBitArray); + } + + public bool IsUninstantiated(SymbolicFeatureValue sfv, SymbolicFeature feature) + { + return BitArraysAreEqual(sfv.FlagsBitArray, feature.MaskBitArray); + } + + public void Set(SymbolicFeatureValue sfv, IEnumerable symbols) + { + BitArray maskBA = new BitArray(sfv.BitArraySize); + foreach (FeatureSymbol symbol in symbols) + { + maskBA.Set(symbol.Index, true); + sfv.FlagsBitArray.Or(maskBA); + } + } + + public void SetFirst(SymbolicFeatureValue sfv, SymbolicFeature feature) + { + sfv.First = HasAnySet(sfv.FlagsBitArray) ? feature.PossibleSymbols.First(sfv.Get) : null; + } + + public void IntersectWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + if (!not && !notOther) + { + sfv.FlagsBitArray = new BitArray(sfv.FlagsBitArray.And(otherSfv.FlagsBitArray)); + } + else + { + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray notOtherFlagsAndFeatureMask = new BitArray( + new BitArray(otherSfv.FlagsBitArray).Not().And(feature.MaskBitArray) + ); + if (!not) + { + sfv.FlagsBitArray = new BitArray(sfv.FlagsBitArray.And(notOtherFlagsAndFeatureMask)); + } + else + { + BitArray notFlagsAndFeatureMask = new BitArray( + new BitArray(sfv.FlagsBitArray).Not().And(feature.MaskBitArray) + ); + if (!notOther) + { + sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray)); + } + else + { + sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask)); + } + } + } + } + + public bool IsSupersetOf( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(sfv.FlagsBitArray); + BitArray otherFlags = new BitArray(otherSfv.FlagsBitArray); + if (!not && !notOther) + { + return BitArraysAreEqual(flags.And(otherFlags), otherSfv.FlagsBitArray); + } + else + { + BitArray notOtherFlagsAndFeatureMask = new BitArray( + new BitArray(otherFlags).Not().And(feature.MaskBitArray) + ); + if (!not) + { + return BitArraysAreEqual(flags.And(notOtherFlagsAndFeatureMask), notOtherFlagsAndFeatureMask); + } + else + { + BitArray notFlags = new BitArray(sfv.FlagsBitArray).Not(); + BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(feature.MaskBitArray)); + if (!notOther) + { + return BitArraysAreEqual( + notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray), + otherSfv.FlagsBitArray + ); + } + else + { + return BitArraysAreEqual( + notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask), + notOtherFlagsAndFeatureMask + ); + } + } + } + } + + public bool Overlaps( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(sfv.FlagsBitArray); + BitArray otherFlags = new BitArray(otherSfv.FlagsBitArray); + if (!not && !notOther) + { + return HasAnySet(flags.And(otherFlags)); + } + else + { + BitArray notOtherFlags = new BitArray(otherFlags).Not(); + BitArray notOtherFlagsAndFeatureMask = new BitArray(notOtherFlags.And(feature.MaskBitArray)); + if (!not) + { + return HasAnySet(flags.And(notOtherFlagsAndFeatureMask)); + } + else + { + BitArray notFlags = new BitArray(sfv.FlagsBitArray).Not(); + BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(feature.MaskBitArray)); + if (!notOther) + { + return HasAnySet(notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray)); + } + else + { + return HasAnySet((notFlagsAndFeatureMask).And(notOtherFlagsAndFeatureMask)); + } + } + } + } + + public void UnionWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables + BitArray flags = new BitArray(sfv.FlagsBitArray); + BitArray otherFlags = new BitArray(otherSfv.FlagsBitArray); + if (!not && !notOther) + { + sfv.FlagsBitArray = new BitArray(flags.Or(otherFlags)); + } + else + { + BitArray notOtherFlagsAndFeatureMask = new BitArray( + new BitArray(otherFlags).Not().And(feature.MaskBitArray) + ); + if (!not) + { + sfv.FlagsBitArray = new BitArray(sfv.FlagsBitArray.Or(notOtherFlagsAndFeatureMask)); + } + else + { + BitArray notFlagsAndFeatureMask = new BitArray(new BitArray(flags).Not().And(feature.MaskBitArray)); + if (!notOther) + { + sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.Or(otherFlags)); + } + else + { + sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.Or(notOtherFlagsAndFeatureMask)); + } + } + } + } + + public bool ValueEquals(SymbolicFeatureValue sfv, SymbolicFeatureValue otherSfv) + { + return BitArraysAreEqual(sfv.FlagsBitArray, otherSfv.FlagsBitArray); + } + + private bool BitArraysAreEqual(BitArray array1, BitArray array2) + { + return array1.Cast().SequenceEqual(array2.Cast()); + } + + // TODO: replace with C# version when available (starting with v.8) + private bool HasAnySet(BitArray mask) + { + foreach (bool flag in mask) + { + if (flag) + return true; + } + return false; + } + } +} diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs new file mode 100644 index 000000000..6eeb88b8d --- /dev/null +++ b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SIL.Machine.FeatureModel +{ + internal class SymbolicFeatureValueUlong : ISymbolicFeatureValue + { + public void ExceptWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + if (!not && !notOther) + sfv.FlagsUlong = sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong); + else if (!not) + sfv.FlagsUlong = sfv.FlagsUlong & otherSfv.FlagsUlong; + else if (!notOther) + sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong); + else + sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong; + } + + public bool Get(SymbolicFeatureValue sfv, FeatureSymbol symbol) + { + return (sfv.FlagsUlong & (1UL << symbol.Index)) != 0; + } + + public int GetValuesHashCode(SymbolicFeatureValue sfv, int code) + { + return code * 31 + sfv.FlagsUlong.GetHashCode(); + } + + public void IntersectWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + if (!not && !notOther) + sfv.FlagsUlong = sfv.FlagsUlong & otherSfv.FlagsUlong; + else if (!not) + sfv.FlagsUlong = sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong); + else if (!notOther) + sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong; + else + sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong); + } + + public bool IsSatisfiable(SymbolicFeatureValue sfv) + { + return sfv.FlagsUlong != 0; + } + + public bool IsSupersetOf( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + if (!not && !notOther) + return (sfv.FlagsUlong & otherSfv.FlagsUlong) == otherSfv.FlagsUlong; + if (!not) + { + return (sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong)) + == (~otherSfv.FlagsUlong & feature.MaskUlong); + } + if (!notOther) + return ((~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong) == otherSfv.FlagsUlong; + return ((~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong)) + == (~otherSfv.FlagsUlong & feature.MaskUlong); + } + + public bool IsUninstantiated(SymbolicFeatureValue sfv, SymbolicFeature feature) + { + return sfv.FlagsUlong == feature.MaskUlong; + } + + public bool Overlaps( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + if (!not && !notOther) + return (sfv.FlagsUlong & otherSfv.FlagsUlong) != 0; + if (!not) + return (sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong)) != 0; + if (!notOther) + return ((~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong) != 0; + return ((~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong)) != 0; + } + + public void Set(SymbolicFeatureValue sfv, IEnumerable symbols) + { + foreach (FeatureSymbol symbol in symbols) + { + ulong mask = 1UL << symbol.Index; + sfv.FlagsUlong |= mask; + } + } + + public void SetFirst(SymbolicFeatureValue sfv, SymbolicFeature feature) + { + sfv.First = sfv.FlagsUlong == 0 ? null : feature.PossibleSymbols.First(sfv.Get); + } + + public void UnionWith( + bool not, + SymbolicFeatureValue sfv, + SymbolicFeatureValue otherSfv, + bool notOther, + SymbolicFeature feature + ) + { + if (!not && !notOther) + sfv.FlagsUlong = sfv.FlagsUlong | otherSfv.FlagsUlong; + else if (!not) + sfv.FlagsUlong = sfv.FlagsUlong | (~otherSfv.FlagsUlong & feature.MaskUlong); + else if (!notOther) + sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) | otherSfv.FlagsUlong; + else + sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) | (~otherSfv.FlagsUlong & feature.MaskUlong); + } + + public bool ValueEquals(SymbolicFeatureValue sfv, SymbolicFeatureValue otherSfv) + { + return sfv.FlagsUlong == otherSfv.FlagsUlong; + } + } +} diff --git a/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs b/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs index 16c0bd513..fd5f49d0a 100644 --- a/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs +++ b/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs @@ -21,7 +21,7 @@ public void Unify() { return fs1.Unify(fs2, varBindings, out FeatureStruct res) ? res : null; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -205,7 +205,7 @@ public void IsUnifiable() Func resultsSelector = (fs1, fs2) => fs1.IsUnifiable(fs2); Func varResultsSelector = (fs1, fs2, varBindings) => fs1.IsUnifiable(fs2, varBindings); - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( EqualityComparer.Default, resultsSelector, varResultsSelector, @@ -256,7 +256,7 @@ public void PriorityUnion() fs1.PriorityUnion(fs2, varBindings); return fs1; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -412,7 +412,7 @@ public void Union() return fs1; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -490,7 +490,7 @@ public void Subtract() return fs1; }; - TestBinaryOperation( + TestBinaryOperationUlongAndBitArray( FreezableEqualityComparer.Default, resultsSelector, varResultsSelector, @@ -589,6 +589,21 @@ public void Subtract() ); } + private static void TestBinaryOperationUlongAndBitArray( + IEqualityComparer comparer, + Func resultsSelector, + Func varResultsSelector, + params Func[] expectedSelectors + ) + { + // Use default ulong for values + TestBinaryOperation(comparer, resultsSelector, varResultsSelector, expectedSelectors); + // Use BitArray for values + SymbolicFeatureValue.NeedToUseBitArray = 0; + TestBinaryOperation(comparer, resultsSelector, varResultsSelector, expectedSelectors); + SymbolicFeatureValue.NeedToUseBitArray = sizeof(ulong) * 8; + } + private static void TestBinaryOperation( IEqualityComparer comparer, Func resultsSelector, @@ -897,4 +912,124 @@ params Func[] expectedSelectors Is.EqualTo(expectedSelectors[21](featSys)).Using(comparer) ); } + + [Test] + public void BitArray() + { + // Parts of Speech for Ket has more than 64 values. + // Using ulong alone in SymbolicFeatureValue can result in extra values being matched. + var featSys = new FeatureSystem + { + new SymbolicFeature( + "POS", + new FeatureSymbol("adv"), + new FeatureSymbol("adv.inc"), + new FeatureSymbol("n"), + new FeatureSymbol("nprop"), + new FeatureSymbol("n.inc"), + new FeatureSymbol("pro"), + new FeatureSymbol("pers"), + new FeatureSymbol("dempro"), + new FeatureSymbol("posspro"), + new FeatureSymbol("reflpro"), + new FeatureSymbol("que"), + new FeatureSymbol("adj"), + new FeatureSymbol("adj.inc"), + new FeatureSymbol("actn"), + new FeatureSymbol("v"), + new FeatureSymbol("v.inc"), + new FeatureSymbol("vi"), + new FeatureSymbol("v1"), + new FeatureSymbol("v1.aoln"), + new FeatureSymbol("v1.ao"), + new FeatureSymbol("v1.sln"), + new FeatureSymbol("v1.ln"), + new FeatureSymbol("v2"), + new FeatureSymbol("v2.aoln"), + new FeatureSymbol("v2.ao"), + new FeatureSymbol("v2.sln"), + new FeatureSymbol("v2.ln"), + new FeatureSymbol("v3"), + new FeatureSymbol("v3.aoln"), + new FeatureSymbol("v3.ao"), + new FeatureSymbol("v3.sln"), + new FeatureSymbol("v3.ln"), + new FeatureSymbol("v4"), + new FeatureSymbol("v4.aoln"), + new FeatureSymbol("v4.ao"), + new FeatureSymbol("v4.sln"), + new FeatureSymbol("v4.ln"), + new FeatureSymbol("v5"), + new FeatureSymbol("v5.aoln"), + new FeatureSymbol("v5.ao"), + new FeatureSymbol("v5.sln"), + new FeatureSymbol("v5.ln"), + new FeatureSymbol("v2.appl"), + new FeatureSymbol("v2.appl.ao"), + new FeatureSymbol("v2.appl.aoln"), + new FeatureSymbol("vt"), + new FeatureSymbol("vt1"), + new FeatureSymbol("vt1.aoln"), + new FeatureSymbol("vt1.ao"), + new FeatureSymbol("vt1.sln"), + new FeatureSymbol("vt1.ln"), + new FeatureSymbol("vt2"), + new FeatureSymbol("vt2.aoln"), + new FeatureSymbol("vt2.ao"), + new FeatureSymbol("vt2.sln"), + new FeatureSymbol("vt2.ln"), + new FeatureSymbol("vt3"), + new FeatureSymbol("vt3.aoln"), + new FeatureSymbol("vt3.ao"), + new FeatureSymbol("vt3.sln"), + new FeatureSymbol("vt3.ln"), + new FeatureSymbol("vt4"), + new FeatureSymbol("vt4.aoln"), + new FeatureSymbol("vt4.ao"), + new FeatureSymbol("vt4.sln"), + new FeatureSymbol("vt4.ln"), + new FeatureSymbol("vt2/appl"), + new FeatureSymbol("vt2.appl.aoln"), + new FeatureSymbol("vt2.appl.ln"), + new FeatureSymbol("vt1.appl"), + new FeatureSymbol("vt1.appl.ln"), + new FeatureSymbol("v.irr"), + new FeatureSymbol("ptcl"), + new FeatureSymbol("sub"), + new FeatureSymbol("pp"), + new FeatureSymbol("vpred"), + new FeatureSymbol("conj"), + new FeatureSymbol("interj"), + new FeatureSymbol("num"), + new FeatureSymbol("ncp") + ) + }; + featSys.Freeze(); + // 80 values + CheckFirstAndLastValues(featSys, "adv"); + + SymbolicFeature featPos = (SymbolicFeature)featSys.GetFeature("POS"); + // 65 values + SkipAndCheck(featPos, 15, "v.inc"); + // 64 values ( = sizeof(ulong) ) + SkipAndCheck(featPos, 16, "vi"); + // 63 values + SkipAndCheck(featPos, 17, "v1"); + } + + private static void SkipAndCheck(SymbolicFeature featPos, int iSkip, string sFirst) + { + var symbols = featPos.PossibleSymbols.Skip(iSkip); + Assert.That(symbols.Count, Is.EqualTo(80 - iSkip)); + FeatureSystem featSys = new FeatureSystem { new SymbolicFeature("POS", symbols) }; + CheckFirstAndLastValues(featSys, sFirst); + } + + private static void CheckFirstAndLastValues(FeatureSystem featSys, string sFirst) + { + FeatureStruct fs1 = FeatureStruct.NewMutable(featSys).Symbol(sFirst).Value; + Assert.That(fs1.ToString(), Is.EqualTo("[POS:" + sFirst + "]")); + FeatureStruct fs2 = FeatureStruct.NewMutable(featSys).Symbol("ncp").Value; + Assert.That(fs2.ToString(), Is.EqualTo("[POS:ncp]")); + } } From dcfc257f691d21a37c1ddc650e999e032dc6523d Mon Sep 17 00:00:00 2001 From: Damien Daspit Date: Tue, 2 Sep 2025 12:18:42 -0400 Subject: [PATCH 2/2] Refactor to encapsulate flags data structure - rename interfaces and classes --- .../BitArraySymbolicFeatureValueFlags.cs | 216 ++++++++++++++ .../FeatureModel/ISymbolicFeatureValue.cs | 50 ---- .../ISymbolicFeatureValueFlags.cs | 27 ++ .../FeatureModel/SymbolicFeature.cs | 22 -- .../FeatureModel/SymbolicFeatureValue.cs | 215 +++----------- .../SymbolicFeatureValueBitArray.cs | 275 ------------------ .../FeatureModel/SymbolicFeatureValueUlong.cs | 139 --------- .../UlongSymbolicFeatureValueFlags.cs | 138 +++++++++ src/SIL.Machine/Properties/AssemblyInfo.cs | 3 + .../FeatureModel/FeatureStructTests.cs | 4 +- 10 files changed, 433 insertions(+), 656 deletions(-) create mode 100644 src/SIL.Machine/FeatureModel/BitArraySymbolicFeatureValueFlags.cs delete mode 100644 src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs create mode 100644 src/SIL.Machine/FeatureModel/ISymbolicFeatureValueFlags.cs delete mode 100644 src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs delete mode 100644 src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs create mode 100644 src/SIL.Machine/FeatureModel/UlongSymbolicFeatureValueFlags.cs create mode 100644 src/SIL.Machine/Properties/AssemblyInfo.cs diff --git a/src/SIL.Machine/FeatureModel/BitArraySymbolicFeatureValueFlags.cs b/src/SIL.Machine/FeatureModel/BitArraySymbolicFeatureValueFlags.cs new file mode 100644 index 000000000..451bae29b --- /dev/null +++ b/src/SIL.Machine/FeatureModel/BitArraySymbolicFeatureValueFlags.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace SIL.Machine.FeatureModel +{ + internal class BitArraySymbolicFeatureValueFlags : ISymbolicFeatureValueFlags + { + private readonly SymbolicFeature _feature; + private readonly BitArray _flags; + + public BitArraySymbolicFeatureValueFlags(SymbolicFeature feature) + { + _feature = feature; + _flags = new BitArray(feature.PossibleSymbols.Count, false); + } + + private BitArraySymbolicFeatureValueFlags(SymbolicFeature feature, BitArray flags) + { + _feature = feature; + _flags = flags; + } + + public bool HasAnySet() + { + return HasAnySet(_flags); + } + + public bool HasAllSet() + { + return HasAllSet(_flags); + } + + public bool Get(FeatureSymbol symbol) + { + return _flags.Get(symbol.Index); + } + + public FeatureSymbol GetFirst() + { + return _feature.PossibleSymbols.FirstOrDefault(Get); + } + + public void Set(IEnumerable symbols) + { + foreach (FeatureSymbol symbol in symbols) + _flags.Set(symbol.Index, true); + } + + public bool IsSupersetOf(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherBitArray = (BitArraySymbolicFeatureValueFlags)other; + if (!not && !notOther) + { + return AreEqual(Copy(_flags).And(otherBitArray._flags), otherBitArray._flags); + } + else if (!not) + { + BitArray notOtherFlags = Copy(otherBitArray._flags).Not(); + return AreEqual(Copy(_flags).And(notOtherFlags), notOtherFlags); + } + else if (!notOther) + { + return AreEqual(Copy(_flags).Not().And(otherBitArray._flags), otherBitArray._flags); + } + else + { + BitArray notOtherFlags = Copy(otherBitArray._flags).Not(); + return AreEqual(Copy(_flags).Not().And(notOtherFlags), notOtherFlags); + } + } + + public bool Overlaps(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherBitArray = (BitArraySymbolicFeatureValueFlags)other; + if (!not && !notOther) + { + return HasAnySet(Copy(_flags).And(otherBitArray._flags)); + } + else if (!not) + { + BitArray notOtherFlags = Copy(otherBitArray._flags).Not(); + return AreEqual(Copy(_flags).And(notOtherFlags), notOtherFlags); + } + else if (!notOther) + { + return AreEqual(Copy(_flags).Not().And(otherBitArray._flags), otherBitArray._flags); + } + else + { + BitArray notOtherFlags = Copy(otherBitArray._flags).Not(); + return AreEqual(Copy(_flags).Not().And(notOtherFlags), notOtherFlags); + } + } + + public void IntersectWith(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherBitArray = (BitArraySymbolicFeatureValueFlags)other; + if (!not && !notOther) + { + _flags.And(otherBitArray._flags); + } + else if (!not) + { + _flags.And(Copy(otherBitArray._flags).Not()); + } + else if (!notOther) + { + _flags.Not().And(otherBitArray._flags); + } + else + { + _flags.Not().And(Copy(otherBitArray._flags).Not()); + } + } + + public void UnionWith(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherBitArray = (BitArraySymbolicFeatureValueFlags)other; + if (!not && !notOther) + { + _flags.Or(otherBitArray._flags); + } + else if (!not) + { + _flags.Or(Copy(otherBitArray._flags).Not()); + } + else if (!notOther) + { + _flags.Not().Or(otherBitArray._flags); + } + else + { + _flags.Not().Or(Copy(otherBitArray._flags).Not()); + } + } + + public void ExceptWith(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherBitArray = (BitArraySymbolicFeatureValueFlags)other; + if (!not && !notOther) + { + _flags.And(Copy(otherBitArray._flags).Not()); + } + else if (!not) + { + _flags.And(otherBitArray._flags); + } + else if (!notOther) + { + _flags.Not().And(Copy(otherBitArray._flags).Not()); + } + else + { + _flags.Not().And(otherBitArray._flags); + } + } + + public ISymbolicFeatureValueFlags Not() + { + return new BitArraySymbolicFeatureValueFlags(_feature, Copy(_flags).Not()); + } + + public bool ValueEquals(ISymbolicFeatureValueFlags other) + { + var otherBitArray = (BitArraySymbolicFeatureValueFlags)other; + return AreEqual(_flags, otherBitArray._flags); + } + + public int GetValuesHashCode() + { + int hash = 0; + foreach (bool value in _flags) + { + hash ^= (value ? 2 : 1); + } + return hash; + } + + public ISymbolicFeatureValueFlags Clone() + { + return new BitArraySymbolicFeatureValueFlags(_feature, Copy(_flags)); + } + + private static bool AreEqual(BitArray array1, BitArray array2) + { + return array1.Cast().SequenceEqual(array2.Cast()); + } + + private static bool HasAnySet(BitArray flags) + { + foreach (bool flag in flags) + { + if (flag) + return true; + } + return false; + } + + private static bool HasAllSet(BitArray flags) + { + foreach (bool flag in flags) + { + if (!flag) + return false; + } + return true; + } + + private static BitArray Copy(BitArray flags) + { + return new BitArray(flags); + } + } +} diff --git a/src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs b/src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs deleted file mode 100644 index 3bdf4b57a..000000000 --- a/src/SIL.Machine/FeatureModel/ISymbolicFeatureValue.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; - -namespace SIL.Machine.FeatureModel -{ - internal interface ISymbolicFeatureValue - { - void ExceptWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ); - bool Get(SymbolicFeatureValue sfv, FeatureSymbol symbol); - int GetValuesHashCode(SymbolicFeatureValue sfv, int code); - void IntersectWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ); - bool IsSatisfiable(SymbolicFeatureValue sfv); - bool IsSupersetOf( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ); - bool IsUninstantiated(SymbolicFeatureValue sfv, SymbolicFeature feature); - bool Overlaps( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ); - void Set(SymbolicFeatureValue sfv, IEnumerable symbols); - void SetFirst(SymbolicFeatureValue sfv, SymbolicFeature feature); - void UnionWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ); - bool ValueEquals(SymbolicFeatureValue sfv, SymbolicFeatureValue otherSFv); - } -} diff --git a/src/SIL.Machine/FeatureModel/ISymbolicFeatureValueFlags.cs b/src/SIL.Machine/FeatureModel/ISymbolicFeatureValueFlags.cs new file mode 100644 index 000000000..7011774d7 --- /dev/null +++ b/src/SIL.Machine/FeatureModel/ISymbolicFeatureValueFlags.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using SIL.ObjectModel; + +namespace SIL.Machine.FeatureModel +{ + internal interface ISymbolicFeatureValueFlags : ICloneable + { + bool HasAnySet(); + bool HasAllSet(); + + bool Get(FeatureSymbol symbol); + FeatureSymbol GetFirst(); + void Set(IEnumerable symbols); + + bool IsSupersetOf(bool not, ISymbolicFeatureValueFlags other, bool notOther); + bool Overlaps(bool not, ISymbolicFeatureValueFlags other, bool notOther); + + void IntersectWith(bool not, ISymbolicFeatureValueFlags other, bool notOther); + void UnionWith(bool not, ISymbolicFeatureValueFlags other, bool notOther); + void ExceptWith(bool not, ISymbolicFeatureValueFlags other, bool notOther); + + ISymbolicFeatureValueFlags Not(); + + bool ValueEquals(ISymbolicFeatureValueFlags other); + int GetValuesHashCode(); + } +} diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeature.cs b/src/SIL.Machine/FeatureModel/SymbolicFeature.cs index e627253e1..4fad07741 100644 --- a/src/SIL.Machine/FeatureModel/SymbolicFeature.cs +++ b/src/SIL.Machine/FeatureModel/SymbolicFeature.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.Collections.Generic; using SIL.ObjectModel; @@ -7,8 +6,6 @@ namespace SIL.Machine.FeatureModel public class SymbolicFeature : Feature { private readonly PossibleSymbolCollection _possibleSymbols; - private readonly ulong _mask; - private readonly BitArray _maskBA = new BitArray(sizeof(ulong) * 8, false); public SymbolicFeature(string id, params FeatureSymbol[] possibleSymbols) : this(id, (IEnumerable)possibleSymbols) { } @@ -23,16 +20,6 @@ public SymbolicFeature(string id, IEnumerable possibleSymbols) symbol.Feature = this; symbol.Index = i++; } - - int symbolCount = _possibleSymbols.Count; - if (symbolCount > SymbolicFeatureValue.NeedToUseBitArray) - { - _maskBA = new BitArray(symbolCount, true); - } - else - { - _mask = (1UL << symbolCount) - 1UL; - } } /// @@ -48,14 +35,5 @@ public string DefaultSymbolID { set { DefaultValue = new SymbolicFeatureValue(_possibleSymbols[value]); } } - - internal ulong MaskUlong - { - get { return _mask; } - } - internal BitArray MaskBitArray - { - get { return _maskBA; } - } } } diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs index c2cf99e04..724911e0a 100644 --- a/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs +++ b/src/SIL.Machine/FeatureModel/SymbolicFeatureValue.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -17,27 +16,36 @@ public static implicit operator SymbolicFeatureValue(FeatureSymbol symbol) public static explicit operator FeatureSymbol(SymbolicFeatureValue sfv) { - return sfv.First; + return sfv._first; } - private SymbolicFeatureValueUlong _sfvUlong; - private SymbolicFeatureValueBitArray _sfvBitArray; - private readonly SymbolicFeature _feature; - private ulong _flagsUlong; - private FeatureSymbol _first; - private BitArray _flagsBitArray = null; - private int _bitArraySize = 0; + private static ISymbolicFeatureValueFlags CreateFlags(SymbolicFeature feature) + { + if (feature.PossibleSymbols.Count <= MaxUlongSize) + return new UlongSymbolicFeatureValueFlags(feature); + return new BitArraySymbolicFeatureValueFlags(feature); + } + + internal static void ForceBitArrayFlagsImplementation() + { + MaxUlongSize = 0; + } - // this can be set to 0 for testing the BitArray code - public static int NeedToUseBitArray { get; set; } = sizeof(ulong) * 8; + internal static void ResetFlagsImplementation() + { + MaxUlongSize = sizeof(ulong) * 8; + } - // To test using the BitArray method, comment the preceding line and uncomment the following line - //public static int NeedToUseBitArray { get; set; } = 0; + private static int MaxUlongSize { get; set; } = sizeof(ulong) * 8; + + private readonly SymbolicFeature _feature; + private readonly ISymbolicFeatureValueFlags _flags; + private FeatureSymbol _first; public SymbolicFeatureValue(SymbolicFeature feature) { - MakeValueItemToUse(feature.PossibleSymbols.Count); _feature = feature; + _flags = CreateFlags(_feature); } public SymbolicFeatureValue(IEnumerable values) @@ -46,76 +54,44 @@ public SymbolicFeatureValue(IEnumerable values) if (symbols.Length == 0) throw new ArgumentException("values cannot be empty", "values"); _feature = symbols[0].Feature; - MakeValueItemToUse(_feature.PossibleSymbols.Count); - First = symbols[0]; - Set(symbols); + _flags = CreateFlags(_feature); + _flags.Set(symbols); + _first = symbols[0]; } public SymbolicFeatureValue(FeatureSymbol value) { _feature = value.Feature; - MakeValueItemToUse(_feature.PossibleSymbols.Count); - First = value; - Set(value.ToEnumerable()); + _flags = CreateFlags(_feature); + _flags.Set(value.ToEnumerable()); + _first = value; } public SymbolicFeatureValue(SymbolicFeature feature, string varName, bool agree) : base(varName, agree) { - MakeValueItemToUse(feature.PossibleSymbols.Count); _feature = feature; + _flags = CreateFlags(_feature); } private SymbolicFeatureValue(SymbolicFeatureValue sfv) : base(sfv) { _feature = sfv._feature; - MakeValueItemToUse(_feature.PossibleSymbols.Count); - First = sfv.First; - FlagsUlong = sfv.FlagsUlong; - if (sfv.FlagsBitArray != null) - FlagsBitArray = new BitArray(sfv.FlagsBitArray); + _flags = sfv._flags.Clone(); + _first = sfv._first; } - private SymbolicFeatureValue(SymbolicFeature feature, ulong flags) + private SymbolicFeatureValue(SymbolicFeature feature, ISymbolicFeatureValueFlags flags) { - MakeValueItemToUse(feature.PossibleSymbols.Count); _feature = feature; - FlagsUlong = flags; + _flags = flags; SetFirst(); } - public SymbolicFeatureValue(SymbolicFeature feature, BitArray notFlagsAndFeatureMask) - : this(feature) - { - FlagsBitArray = new BitArray(notFlagsAndFeatureMask); - } - - private void MakeValueItemToUse(int size) - { - BitArraySize = size; - if (BitArraySize <= NeedToUseBitArray) - { - SfvUlong = new SymbolicFeatureValueUlong(); - } - else - { - _flagsBitArray = new BitArray(BitArraySize, false); - SfvBitArray = new SymbolicFeatureValueBitArray(); - } - } - - private void Set(IEnumerable symbols) - { - if (SfvUlong != null) - SfvUlong.Set(this, symbols); - else - SfvBitArray.Set(this, symbols); - } - public IEnumerable Values { - get { return _feature.PossibleSymbols.Where(Get); } + get { return _feature.PossibleSymbols.Where(_flags.Get); } } public bool IsSupersetOf(SymbolicFeatureValue other, bool notOther = false) @@ -130,18 +106,7 @@ public bool Overlaps(SymbolicFeatureValue other, bool notOther = false) private void SetFirst() { - if (SfvUlong != null) - SfvUlong.SetFirst(this, _feature); - else - SfvBitArray.SetFirst(this, _feature); - } - - public bool Get(FeatureSymbol symbol) - { - if (SfvUlong != null) - return SfvUlong.Get(this, symbol); - else - return SfvBitArray.Get(this, symbol); + _first = _flags.GetFirst(); } public SymbolicFeature Feature @@ -151,62 +116,12 @@ public SymbolicFeature Feature protected override bool IsSatisfiable { - get - { - bool sfvValue = false; - if (SfvUlong != null) - sfvValue = SfvUlong.IsSatisfiable(this); - else - sfvValue = SfvBitArray.IsSatisfiable(this); - return base.IsSatisfiable || sfvValue; - } + get { return base.IsSatisfiable || _flags.HasAnySet(); } } protected override bool IsUninstantiated { - get - { - bool sfvValue = false; - if (SfvUlong != null) - sfvValue = SfvUlong.IsUninstantiated(this, _feature); - else - sfvValue = SfvBitArray.IsUninstantiated(this, _feature); - return base.IsUninstantiated && sfvValue; - } - } - - public BitArray FlagsBitArray - { - get => _flagsBitArray; - set => _flagsBitArray = value; - } - - public ulong FlagsUlong - { - get => _flagsUlong; - set => _flagsUlong = value; - } - public FeatureSymbol First - { - get => _first; - set => _first = value; - } - - internal SymbolicFeatureValueUlong SfvUlong - { - get => _sfvUlong; - set => _sfvUlong = value; - } - - internal SymbolicFeatureValueBitArray SfvBitArray - { - get => _sfvBitArray; - set => _sfvBitArray = value; - } - public int BitArraySize - { - get => _bitArraySize; - set => _bitArraySize = value; + get { return base.IsUninstantiated && _flags.HasAllSet(); } } protected override bool IsSupersetOf(bool not, SimpleFeatureValue other, bool notOther) @@ -214,10 +129,7 @@ protected override bool IsSupersetOf(bool not, SimpleFeatureValue other, bool no if (!(other is SymbolicFeatureValue otherSfv)) return false; - if (SfvUlong != null) - return SfvUlong.IsSupersetOf(not, this, otherSfv, notOther, _feature); - else - return SfvBitArray.IsSupersetOf(not, this, otherSfv, notOther, _feature); + return _flags.IsSupersetOf(not, otherSfv._flags, notOther); } protected override bool Overlaps(bool not, SimpleFeatureValue other, bool notOther) @@ -225,10 +137,7 @@ protected override bool Overlaps(bool not, SimpleFeatureValue other, bool notOth if (!(other is SymbolicFeatureValue otherSfv)) return false; - if (SfvUlong != null) - return SfvUlong.Overlaps(not, this, otherSfv, notOther, _feature); - else - return SfvBitArray.Overlaps(not, this, otherSfv, notOther, _feature); + return _flags.Overlaps(not, otherSfv._flags, notOther); } protected override void IntersectWith(bool not, SimpleFeatureValue other, bool notOther) @@ -236,10 +145,7 @@ protected override void IntersectWith(bool not, SimpleFeatureValue other, bool n if (!(other is SymbolicFeatureValue otherSfv)) return; - if (SfvUlong != null) - SfvUlong.IntersectWith(not, this, otherSfv, notOther, _feature); - else - SfvBitArray.IntersectWith(not, this, otherSfv, notOther, _feature); + _flags.IntersectWith(not, otherSfv._flags, notOther); SetFirst(); } @@ -248,11 +154,7 @@ protected override void UnionWith(bool not, SimpleFeatureValue other, bool notOt if (!(other is SymbolicFeatureValue otherSfv)) return; - if (SfvUlong != null) - SfvUlong.UnionWith(not, this, otherSfv, notOther, _feature); - else - SfvBitArray.UnionWith(not, this, otherSfv, notOther, _feature); - + _flags.UnionWith(not, otherSfv._flags, notOther); SetFirst(); } @@ -261,10 +163,7 @@ protected override void ExceptWith(bool not, SimpleFeatureValue other, bool notO if (!(other is SymbolicFeatureValue otherSfv)) return; - if (SfvUlong != null) - SfvUlong.ExceptWith(not, this, otherSfv, notOther, _feature); - else - SfvBitArray.ExceptWith(not, this, otherSfv, notOther, _feature); + _flags.ExceptWith(not, otherSfv._flags, notOther); } protected override SimpleFeatureValue CloneImpl() @@ -274,21 +173,9 @@ protected override SimpleFeatureValue CloneImpl() public override SimpleFeatureValue Negation() { - if (SfvUlong != null) - { - return IsVariable - ? new SymbolicFeatureValue(_feature, VariableName, !Agree) - : new SymbolicFeatureValue(_feature, (~FlagsUlong & _feature.MaskUlong)); - } - else - { - // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables - BitArray flags = new BitArray(_flagsBitArray); - BitArray notFlagsAndFeatureMask = flags.Not().And(_feature.MaskBitArray); - return IsVariable - ? new SymbolicFeatureValue(_feature, VariableName, !Agree) - : new SymbolicFeatureValue(_feature, notFlagsAndFeatureMask); - } + return IsVariable + ? new SymbolicFeatureValue(_feature, VariableName, !Agree) + : new SymbolicFeatureValue(_feature, _flags.Not()); } public override bool ValueEquals(SimpleFeatureValue other) @@ -301,21 +188,13 @@ public bool ValueEquals(SymbolicFeatureValue other) if (other == null) return false; - bool sfvValue = false; - if (SfvUlong != null) - sfvValue = SfvUlong.ValueEquals(this, other); - else - sfvValue = SfvBitArray.ValueEquals(this, other); - return base.ValueEquals(other) && sfvValue; + return base.ValueEquals(other) && _flags.ValueEquals(other._flags); } protected override int GetValuesHashCode() { int code = base.GetValuesHashCode(); - if (SfvUlong != null) - return SfvUlong.GetValuesHashCode(this, code); - else - return SfvBitArray.GetValuesHashCode(this, code); + return code * 31 + _flags.GetValuesHashCode(); } public new SymbolicFeatureValue Clone() diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs deleted file mode 100644 index 473aff013..000000000 --- a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueBitArray.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace SIL.Machine.FeatureModel -{ - internal class SymbolicFeatureValueBitArray : ISymbolicFeatureValue - { - public void ExceptWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables - BitArray notOtherFlagsAndFeatureMask = new BitArray( - new BitArray(otherSfv.FlagsBitArray).Not().And(feature.MaskBitArray) - ); - if (!not && !notOther) - { - sfv.FlagsBitArray.And(notOtherFlagsAndFeatureMask); - } - else if (!not) - { - sfv.FlagsBitArray.And(otherSfv.FlagsBitArray); - } - else - { - BitArray notFlagsAndFeatureMask = new BitArray( - new BitArray(sfv.FlagsBitArray).Not().And(feature.MaskBitArray) - ); - if (!notOther) - { - sfv.FlagsBitArray = notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask); - } - else - { - sfv.FlagsBitArray = notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray); - } - } - } - - public bool Get(SymbolicFeatureValue sfv, FeatureSymbol symbol) - { - return sfv.FlagsBitArray.Get(symbol.Index); - } - - public int GetValuesHashCode(SymbolicFeatureValue sfv, int code) - { - return code * 31 + GetHashCode(sfv.FlagsBitArray); - } - - public int GetHashCode(BitArray array) - { - int hash = 0; - foreach (bool value in array) - { - hash ^= (value ? 2 : 1); - } - return hash; - } - - public bool IsSatisfiable(SymbolicFeatureValue sfv) - { - return HasAnySet(sfv.FlagsBitArray); - } - - public bool IsUninstantiated(SymbolicFeatureValue sfv, SymbolicFeature feature) - { - return BitArraysAreEqual(sfv.FlagsBitArray, feature.MaskBitArray); - } - - public void Set(SymbolicFeatureValue sfv, IEnumerable symbols) - { - BitArray maskBA = new BitArray(sfv.BitArraySize); - foreach (FeatureSymbol symbol in symbols) - { - maskBA.Set(symbol.Index, true); - sfv.FlagsBitArray.Or(maskBA); - } - } - - public void SetFirst(SymbolicFeatureValue sfv, SymbolicFeature feature) - { - sfv.First = HasAnySet(sfv.FlagsBitArray) ? feature.PossibleSymbols.First(sfv.Get) : null; - } - - public void IntersectWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - if (!not && !notOther) - { - sfv.FlagsBitArray = new BitArray(sfv.FlagsBitArray.And(otherSfv.FlagsBitArray)); - } - else - { - // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables - BitArray notOtherFlagsAndFeatureMask = new BitArray( - new BitArray(otherSfv.FlagsBitArray).Not().And(feature.MaskBitArray) - ); - if (!not) - { - sfv.FlagsBitArray = new BitArray(sfv.FlagsBitArray.And(notOtherFlagsAndFeatureMask)); - } - else - { - BitArray notFlagsAndFeatureMask = new BitArray( - new BitArray(sfv.FlagsBitArray).Not().And(feature.MaskBitArray) - ); - if (!notOther) - { - sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray)); - } - else - { - sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask)); - } - } - } - } - - public bool IsSupersetOf( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables - BitArray flags = new BitArray(sfv.FlagsBitArray); - BitArray otherFlags = new BitArray(otherSfv.FlagsBitArray); - if (!not && !notOther) - { - return BitArraysAreEqual(flags.And(otherFlags), otherSfv.FlagsBitArray); - } - else - { - BitArray notOtherFlagsAndFeatureMask = new BitArray( - new BitArray(otherFlags).Not().And(feature.MaskBitArray) - ); - if (!not) - { - return BitArraysAreEqual(flags.And(notOtherFlagsAndFeatureMask), notOtherFlagsAndFeatureMask); - } - else - { - BitArray notFlags = new BitArray(sfv.FlagsBitArray).Not(); - BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(feature.MaskBitArray)); - if (!notOther) - { - return BitArraysAreEqual( - notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray), - otherSfv.FlagsBitArray - ); - } - else - { - return BitArraysAreEqual( - notFlagsAndFeatureMask.And(notOtherFlagsAndFeatureMask), - notOtherFlagsAndFeatureMask - ); - } - } - } - } - - public bool Overlaps( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables - BitArray flags = new BitArray(sfv.FlagsBitArray); - BitArray otherFlags = new BitArray(otherSfv.FlagsBitArray); - if (!not && !notOther) - { - return HasAnySet(flags.And(otherFlags)); - } - else - { - BitArray notOtherFlags = new BitArray(otherFlags).Not(); - BitArray notOtherFlagsAndFeatureMask = new BitArray(notOtherFlags.And(feature.MaskBitArray)); - if (!not) - { - return HasAnySet(flags.And(notOtherFlagsAndFeatureMask)); - } - else - { - BitArray notFlags = new BitArray(sfv.FlagsBitArray).Not(); - BitArray notFlagsAndFeatureMask = new BitArray(notFlags.And(feature.MaskBitArray)); - if (!notOther) - { - return HasAnySet(notFlagsAndFeatureMask.And(otherSfv.FlagsBitArray)); - } - else - { - return HasAnySet((notFlagsAndFeatureMask).And(notOtherFlagsAndFeatureMask)); - } - } - } - } - - public void UnionWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - // Since logical operations on BitArrays change the BitArray variable, we need to create temp variables - BitArray flags = new BitArray(sfv.FlagsBitArray); - BitArray otherFlags = new BitArray(otherSfv.FlagsBitArray); - if (!not && !notOther) - { - sfv.FlagsBitArray = new BitArray(flags.Or(otherFlags)); - } - else - { - BitArray notOtherFlagsAndFeatureMask = new BitArray( - new BitArray(otherFlags).Not().And(feature.MaskBitArray) - ); - if (!not) - { - sfv.FlagsBitArray = new BitArray(sfv.FlagsBitArray.Or(notOtherFlagsAndFeatureMask)); - } - else - { - BitArray notFlagsAndFeatureMask = new BitArray(new BitArray(flags).Not().And(feature.MaskBitArray)); - if (!notOther) - { - sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.Or(otherFlags)); - } - else - { - sfv.FlagsBitArray = new BitArray(notFlagsAndFeatureMask.Or(notOtherFlagsAndFeatureMask)); - } - } - } - } - - public bool ValueEquals(SymbolicFeatureValue sfv, SymbolicFeatureValue otherSfv) - { - return BitArraysAreEqual(sfv.FlagsBitArray, otherSfv.FlagsBitArray); - } - - private bool BitArraysAreEqual(BitArray array1, BitArray array2) - { - return array1.Cast().SequenceEqual(array2.Cast()); - } - - // TODO: replace with C# version when available (starting with v.8) - private bool HasAnySet(BitArray mask) - { - foreach (bool flag in mask) - { - if (flag) - return true; - } - return false; - } - } -} diff --git a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs b/src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs deleted file mode 100644 index 6eeb88b8d..000000000 --- a/src/SIL.Machine/FeatureModel/SymbolicFeatureValueUlong.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace SIL.Machine.FeatureModel -{ - internal class SymbolicFeatureValueUlong : ISymbolicFeatureValue - { - public void ExceptWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - if (!not && !notOther) - sfv.FlagsUlong = sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong); - else if (!not) - sfv.FlagsUlong = sfv.FlagsUlong & otherSfv.FlagsUlong; - else if (!notOther) - sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong); - else - sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong; - } - - public bool Get(SymbolicFeatureValue sfv, FeatureSymbol symbol) - { - return (sfv.FlagsUlong & (1UL << symbol.Index)) != 0; - } - - public int GetValuesHashCode(SymbolicFeatureValue sfv, int code) - { - return code * 31 + sfv.FlagsUlong.GetHashCode(); - } - - public void IntersectWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - if (!not && !notOther) - sfv.FlagsUlong = sfv.FlagsUlong & otherSfv.FlagsUlong; - else if (!not) - sfv.FlagsUlong = sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong); - else if (!notOther) - sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong; - else - sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong); - } - - public bool IsSatisfiable(SymbolicFeatureValue sfv) - { - return sfv.FlagsUlong != 0; - } - - public bool IsSupersetOf( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - if (!not && !notOther) - return (sfv.FlagsUlong & otherSfv.FlagsUlong) == otherSfv.FlagsUlong; - if (!not) - { - return (sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong)) - == (~otherSfv.FlagsUlong & feature.MaskUlong); - } - if (!notOther) - return ((~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong) == otherSfv.FlagsUlong; - return ((~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong)) - == (~otherSfv.FlagsUlong & feature.MaskUlong); - } - - public bool IsUninstantiated(SymbolicFeatureValue sfv, SymbolicFeature feature) - { - return sfv.FlagsUlong == feature.MaskUlong; - } - - public bool Overlaps( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - if (!not && !notOther) - return (sfv.FlagsUlong & otherSfv.FlagsUlong) != 0; - if (!not) - return (sfv.FlagsUlong & (~otherSfv.FlagsUlong & feature.MaskUlong)) != 0; - if (!notOther) - return ((~sfv.FlagsUlong & feature.MaskUlong) & otherSfv.FlagsUlong) != 0; - return ((~sfv.FlagsUlong & feature.MaskUlong) & (~otherSfv.FlagsUlong & feature.MaskUlong)) != 0; - } - - public void Set(SymbolicFeatureValue sfv, IEnumerable symbols) - { - foreach (FeatureSymbol symbol in symbols) - { - ulong mask = 1UL << symbol.Index; - sfv.FlagsUlong |= mask; - } - } - - public void SetFirst(SymbolicFeatureValue sfv, SymbolicFeature feature) - { - sfv.First = sfv.FlagsUlong == 0 ? null : feature.PossibleSymbols.First(sfv.Get); - } - - public void UnionWith( - bool not, - SymbolicFeatureValue sfv, - SymbolicFeatureValue otherSfv, - bool notOther, - SymbolicFeature feature - ) - { - if (!not && !notOther) - sfv.FlagsUlong = sfv.FlagsUlong | otherSfv.FlagsUlong; - else if (!not) - sfv.FlagsUlong = sfv.FlagsUlong | (~otherSfv.FlagsUlong & feature.MaskUlong); - else if (!notOther) - sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) | otherSfv.FlagsUlong; - else - sfv.FlagsUlong = (~sfv.FlagsUlong & feature.MaskUlong) | (~otherSfv.FlagsUlong & feature.MaskUlong); - } - - public bool ValueEquals(SymbolicFeatureValue sfv, SymbolicFeatureValue otherSfv) - { - return sfv.FlagsUlong == otherSfv.FlagsUlong; - } - } -} diff --git a/src/SIL.Machine/FeatureModel/UlongSymbolicFeatureValueFlags.cs b/src/SIL.Machine/FeatureModel/UlongSymbolicFeatureValueFlags.cs new file mode 100644 index 000000000..bdb1596c4 --- /dev/null +++ b/src/SIL.Machine/FeatureModel/UlongSymbolicFeatureValueFlags.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SIL.Machine.FeatureModel +{ + internal class UlongSymbolicFeatureValueFlags : ISymbolicFeatureValueFlags + { + private readonly SymbolicFeature _feature; + private readonly ulong _mask; + private ulong _flags = 0; + + public UlongSymbolicFeatureValueFlags(SymbolicFeature feature) + { + _feature = feature; + _mask = (1UL << feature.PossibleSymbols.Count) - 1UL; + } + + private UlongSymbolicFeatureValueFlags(SymbolicFeature feature, ulong mask, ulong flags) + { + _feature = feature; + _mask = mask; + _flags = flags; + } + + public bool HasAnySet() + { + return _flags != 0; + } + + public bool HasAllSet() + { + return _flags == _mask; + } + + public bool Get(FeatureSymbol symbol) + { + return (_flags & (1UL << symbol.Index)) != 0; + } + + public FeatureSymbol GetFirst() + { + return _flags == 0 ? null : _feature.PossibleSymbols.First(Get); + } + + public void Set(IEnumerable symbols) + { + foreach (FeatureSymbol symbol in symbols) + { + ulong mask = 1UL << symbol.Index; + _flags |= mask; + } + } + + public bool IsSupersetOf(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherUlong = (UlongSymbolicFeatureValueFlags)other; + if (!not && !notOther) + return (_flags & otherUlong._flags) == otherUlong._flags; + if (!not) + return (_flags & (~otherUlong._flags & _mask)) == (~otherUlong._flags & _mask); + if (!notOther) + return ((~_flags & _mask) & otherUlong._flags) == otherUlong._flags; + return ((~_flags & _mask) & (~otherUlong._flags & _mask)) == (~otherUlong._flags & _mask); + } + + public bool Overlaps(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherUlong = (UlongSymbolicFeatureValueFlags)other; + if (!not && !notOther) + return (_flags & otherUlong._flags) != 0; + if (!not) + return (_flags & (~otherUlong._flags & _mask)) != 0; + if (!notOther) + return ((~_flags & _mask) & otherUlong._flags) != 0; + return ((~_flags & _mask) & (~otherUlong._flags & _mask)) != 0; + } + + public void IntersectWith(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherUlong = (UlongSymbolicFeatureValueFlags)other; + if (!not && !notOther) + _flags = _flags & otherUlong._flags; + else if (!not) + _flags = _flags & (~otherUlong._flags & _mask); + else if (!notOther) + _flags = (~_flags & _mask) & otherUlong._flags; + else + _flags = (~_flags & _mask) & (~otherUlong._flags & _mask); + } + + public void UnionWith(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherUlong = (UlongSymbolicFeatureValueFlags)other; + if (!not && !notOther) + _flags = _flags | otherUlong._flags; + else if (!not) + _flags = _flags | (~otherUlong._flags & _mask); + else if (!notOther) + _flags = (~_flags & _mask) | otherUlong._flags; + else + _flags = (~_flags & _mask) | (~otherUlong._flags & _mask); + } + + public void ExceptWith(bool not, ISymbolicFeatureValueFlags other, bool notOther) + { + var otherUlong = (UlongSymbolicFeatureValueFlags)other; + if (!not && !notOther) + _flags = _flags & (~otherUlong._flags & _mask); + else if (!not) + _flags = _flags & otherUlong._flags; + else if (!notOther) + _flags = (~_flags & _mask) & (~otherUlong._flags & _mask); + else + _flags = (~_flags & _mask) & otherUlong._flags; + } + + public ISymbolicFeatureValueFlags Not() + { + return new UlongSymbolicFeatureValueFlags(_feature, _mask, ~_flags & _mask); + } + + public bool ValueEquals(ISymbolicFeatureValueFlags other) + { + var otherUlong = (UlongSymbolicFeatureValueFlags)other; + return _flags == otherUlong._flags; + } + + public int GetValuesHashCode() + { + return _flags.GetHashCode(); + } + + public ISymbolicFeatureValueFlags Clone() + { + return new UlongSymbolicFeatureValueFlags(_feature, _mask, _flags); + } + } +} diff --git a/src/SIL.Machine/Properties/AssemblyInfo.cs b/src/SIL.Machine/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..a9bd9a03f --- /dev/null +++ b/src/SIL.Machine/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("SIL.Machine.Tests")] diff --git a/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs b/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs index fd5f49d0a..3d6ce691b 100644 --- a/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs +++ b/tests/SIL.Machine.Tests/FeatureModel/FeatureStructTests.cs @@ -599,9 +599,9 @@ params Func[] expectedSelectors // Use default ulong for values TestBinaryOperation(comparer, resultsSelector, varResultsSelector, expectedSelectors); // Use BitArray for values - SymbolicFeatureValue.NeedToUseBitArray = 0; + SymbolicFeatureValue.ForceBitArrayFlagsImplementation(); TestBinaryOperation(comparer, resultsSelector, varResultsSelector, expectedSelectors); - SymbolicFeatureValue.NeedToUseBitArray = sizeof(ulong) * 8; + SymbolicFeatureValue.ResetFlagsImplementation(); } private static void TestBinaryOperation(