#!/Usr/bin/env python3
#########################
"""
Python3+ implementation of the bipolarValuedSets module.
Copyright (C) 2025-2026 Raymond Bisdorff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
.. code-block:: pycon
>>> from bipolarValuedSets import RandomBpvSet
>>> X = RandomBpvSet(numberOfElements=5,elementNamePrefix='s',
... undeterminateness=0.1,valuationRange=(-1, 1),ndigits=4,seed=1)
>>> X.showMembershipCharacteristics()
s2: +0.6949
s3: +0.5275
s5: +0.0000
s4: -0.4899
s1: -0.7313
>>> Y = RandomBpvSet(numberOfElements=3,elementNamePrefix='s',
... undeterminateness=0.1,valuationRange=(-1, 1),ndigits=4,seed=2)
>>> Y.showMembershipCharacteristics()
s1: +0.9121
s2: +0.8957
s3: -0.8869
>>> (X|Y).showMembershipCharacteristics()
# Python's set union symbol is |
s1: +0.9121
s2: +0.8957
s3: +0.5275
s5: +0.0000
s4: -0.4899
>>> (X&Y).showMembershipCharacteristics()
# Python's set intersection symbol is &
s2: +0.6949
s1: -0.7313
s3: -0.8869
s4: -1.0000
s5: -1.0000
>>> (X-Y).showMembershipCharacteristics()
# Python's set difference is -
s3: +0.5275
s5: +0.0000
s4: -0.4899
s2: -0.8957
s1: -0.9121
>>> (Y-X).showMembershipCharacteristics()
s1: +0.7313
s2: -0.6949
s3: -0.8869
s4: -1.0000
s5: -1.0000
>>> (Y^X).showMembershipCharacteristics()
# Python's symmetrix difference symbol is ^
# X^Y = (X-Y)|(Y-X) or (X|Y)-(X&Y)
s1: +0.7313
s3: +0.5275
s5: +0.0000
s4: -0.4899
s2: -0.6949
>>> D = Y - X
>>> D.isSubset(Y)
Decimal('0.8869')
>>> D1 = D.strip(InSite=False)
>>> D1.showMembershipCharacteristics()
s1: +0.7313
s2: -0.6949
s3: -0.8869
>>> D2 = D1.polarise(InSite=False)
>>> D2.showMembershipCharacteristics()
s1: +1.0000
s2: -1.0000
s3: -1.0000
See :ref:`Introducing computational bipolar-valued set theory
<Bipolar-Valued-Sets-Tutorial-label>`
"""
#######################
__version__ = "Branch: Python3.14.5$"
from collections import OrderedDict
from decimal import Decimal
#------------------
[docs]
class BpvSet(object):
"""
Root class. See the final scratch part
"""
def __repr__(self):
"""
Default presentation method for bipolar-valued set instances.
"""
reprString = '*------- bvset instance description ------*\n'
reprString += 'Instance class : %s\n' % self.__class__.__name__
reprString += 'Instance name : %s\n' % self.name
reprString += 'support dimension : %s\n' % len(self.support)
try:
reprString += 'seed : %s\n' % str(self.randomSeed)
except:
pass
reprString += 'Positive members : %d\n' % self.computeCardinality()
reprString += 'Valuation domain : [%.2f;%.2f]\n'\
% (self.valuationDomain['min'],self.valuationDomain['max'])
reprString += 'Determinateness (%%) : %.2f\n' % (self.determinateness * Decimal('100.0'))
reprString += 'Attributes : %s\n' % list(self.__dict__.keys())
return reprString
def __init__(self,fileName=None):
from decimal import Decimal
if fileName is None:
self.name = 'emptyBvpSet'
self.support = {}
self.ndigits = 4
self.valuationDomain = {'min':Decimal(-1),'med':Decimal(0),'max':Decimal(1)}
self.membership = {}
self.cardinality = 0
self.determinateness = Decimal('0')
else:
fileNameExt = fileName+'.py'
argDict = {}
fi = open(fileNameExt,'r')
fileText = fi.read()
fi.close()
exec(compile(fileText, fileName, 'exec'), argDict)
self.name = fileNameExt
self.support = argDict['support']
self.ndigits = argDict['ndigits']
self.valuationDomain = argDict['valuationDomain']
self.membership = argDict['membership']
self.cardinality = self.computeCardinality()
self.determinateness = self.computeDeterminateness()
[docs]
def showMembershipCharacteristics(self,ndigits=None,Normalized=False):
"""
When *Normalized*, the charactritic values are recoded into the [-1,1] range
"""
from digraphsTools import scoredTuplesSort
if Normalized:
from copy import deepcopy
cps = deepcopy(self)
cps.recodeValuation()
membership = cps.membership
valuationDomain = cps.valuationDomain
else:
membership = self.membership
valuationDomain = self.valuationDomain
items = self.support
if ndigits is None or ndigits > self.ndigits:
ndigits = self.ndigits
formatString = ' %%s: %%+ .%df' % (ndigits)
characteristics = [(membership[x],x) for x in items]
scoredTuplesSort(characteristics,reverse=True)
#print(characteristics)
for it in characteristics:
#print(it)
x = it[1]
print(formatString% (items[x]['shortName'], membership[x]) )
print('Valuation domain: [%+.2f;%+.2f]' % (
valuationDomain['min'],valuationDomain['max'] ))
[docs]
def showPositiveElements(self,/):
"""
Return the stricly positive members of the set
"""
Sp = self.strip(InSite=False,cutLevel=self.valuationDomain['med'])
Sp.showMembershipCharacteristics()
[docs]
def recodeValuation(self,newMin=-1,newMax=1,ndigits=None,Debug=False):
"""
Specialization for recoding the valuation of a BpvSet membership chracteristics
By default the valuation domain is normalized to [-1;1]
"""
from decimal import Decimal
from copy import deepcopy
# saving old and new valuation domain
oldMax = self.valuationDomain['max']
oldMin = self.valuationDomain['min']
oldMed = self.valuationDomain['med']
oldAmplitude = oldMax - oldMin
if Debug:
print(oldMin, oldMed, oldMax, oldAmplitude)
if ndigits is None:
ndigits = self.ndigits
formatString = '%%2.%df' % ndigits
newMin = Decimal(formatString % newMin)
newMax = Decimal(formatString % newMax)
newMed = Decimal(formatString % ( (newMax + newMin)/Decimal('2.0') ))
newAmplitude = newMax - newMin
if Debug:
print(newMin, newMed, newMax, newAmplitude)
# loop over all items
#print('Recoding the valuation of a BpvSet instance')
oldMembership = self.membership
newMembership = {}
for it in self.support:
newMembership[it] = Decimal(formatString % (
newMin + ((oldMembership[it] - oldMin)/oldAmplitude) * newAmplitude))
# update valuation domain
self.valuationDomain = { 'min':newMin, 'max':newMax, 'med':newMed }
if ndigits == 0:
self.valuationDomain['hasIntegerValuation'] = True
else:
self.valuationDomain['hasIntegerValuation'] = False
self.membership = deepcopy(newMembership)
[docs]
def computeDeterminateness(self):
"""
Return the mean absolute characteristic value
"""
from decimal import Decimal
Med = self.valuationDomain['med']
Max = self.valuationDomain['max']
determinateness = Decimal(0)
items = self.support
n = len(items)
membership = self.membership
for it in items:
determinateness += abs(membership[it])
return determinateness / Decimal(str(n*Max))
[docs]
def computeCardinality(self):
"""
Return the number of positively included elements
"""
cardinality = 0
Med = self.valuationDomain['med']
items = self.support
membership = self.membership
for it in items:
#print(it)
if membership[it] > Med:
cardinality += 1
return cardinality
[docs]
def save(self,fileName='tempBpvSet'):
"""
Permanent storage of a BvpSet instance
"""
print('*--- Saving bvp-set in file: <' + fileName + '.py> ---*')
support = self.support
membership = self.membership
Min = self.valuationDomain['min']
Med = self.valuationDomain['med']
Max = self.valuationDomain['max']
fileNameExt = str(fileName)+str('.py')
fo = open(fileNameExt, 'w')
fo.write('# Saved BvpSet instance\n')
fo.write('from collections import OrderedDict\n')
fo.write('from decimal import Decimal\n')
fo.write('support = OrderedDict([\n')
for x in support:
fo.write('(\'' + str(x) + '\',\n')
try:
fo.write(str(support[x])+'),\n')
except:
fo.write('{\'name\': \'%s\'}),\n' % str(x))
fo.write('])\n')
fo.write('valuationDomain = {\'min\': Decimal("'+str(Min)+'"),\'med\': Decimal("'+str(Med)+'"),\'max\': Decimal("'+str(Max)+'")}\n')
try:
ndigits = self.ndigits
except:
ndigits = 4
fo.write('ndigits = %d \n' % ndigits )
fo.write('membership = {\n')
for x in membership:
fo.write('\'' + str(x) + '\':')
valueString = 'Decimal(\'%%.%df\'),\n' % (ndigits)
fo.write(valueString % membership[x])
fo.write( '}\n')
fo.close()
def __and__(self,other,/):
"""
Return self&other
"""
return self.intersection(other)
[docs]
def intersection(self,other,/):
"""
Return the set intersection of self and other
"""
from copy import deepcopy
from bipolarValuedSets import BpvSet
newSelf = deepcopy(self)
newSelf.recodeValuation()
newOther = deepcopy(other)
newOther.recodeValuation()
inter = BpvSet()
inter.name = self.name+'and'+other.name
# union of the supports
for it in newSelf.support:
#print(it)
if it not in inter.support:
inter.support[it] = newSelf.support[it]
for it in newOther.support:
#print(it)
if it not in inter.support:
inter.support[it] = newOther.support[it]
membership = {}
Min = inter.valuationDomain['min']
for it in inter.support:
try:
membership[it] = min(newSelf.membership[it],newOther.membership[it])
except:
membership[it] = Min
inter.ndigits = max(newSelf.ndigits, newOther.ndigits)
inter.membership = membership
inter.determinateness = inter.computeDeterminateness()
inter.cardinality = inter.computeCardinality()
return inter
def __or__(self,other,/):
""" Return self|other
"""
return self.union(other)
[docs]
def union(self,other,/):
"""
Return the bipolar-valued set union of self and other
"""
from copy import deepcopy
from bipolarValuedSets import BpvSet
newSelf = deepcopy(self)
newSelf.recodeValuation()
newOther = deepcopy(other)
newOther.recodeValuation()
union = BpvSet()
union.name = self.name+'or'+other.name
# union of the supports
for it in newSelf.support:
#print(it)
if it not in union.support:
union.support[it] = newSelf.support[it]
for it in newOther.support:
#print(it)
if it not in union.support:
union.support[it] = newOther.support[it]
membership = {}
Max = union.valuationDomain['max']
for it in union.support:
try:
membership[it] = max(newSelf.membership[it],newOther.membership[it])
except:
try:
membership[it] = newSelf.membership[it]
except:
membership[it] = newOther.membership[it]
union.ndigits = max(newSelf.ndigits, newOther.ndigits)
union.membership = membership
union.determinateness = union.computeDeterminateness()
union.cardinality = union.computeCardinality()
return union
def __contains__(self,it,/):
"""
x.__contains__(y) <==> y in x
x is the identifier
of a potential element
"""
if it in self.support:
return True
else:
return False
[docs]
def isSharper(self,other,Comments=False):
"""
Returns the Bolean value of the test
"""
from copy import deepcopy
newSelf = deepcopy(self)
newSelf.recodeValuation()
newOther = deepcopy(other)
newOther.recodeValuation()
Med = newSelf.valuationDomain['med']
for it in newSelf.support:
if it not in newOther.support:
newOther.support[it] = newSelf.support[it]
newOther.membership[it] = Med
IsSharper = True
for it in newSelf.support:
if Comments:
print(it,newSelf.membership[it],newOther.membership[it])
if (newSelf.membership[it] > newOther.membership[it]) and \
(newSelf.membership[it] <= Med):
IsSharper = False
break
elif (newSelf.membership[it] < newOther.membership[it]) and \
(newSelf.membership[it] >= Med):
IsSharper = False
break
return IsSharper
[docs]
def isSubset(self,other,/):
"""
Return the bipolar-valued credibility that self is a bpv-subset of other
"""
from copy import deepcopy
newSelf = deepcopy(self)
newSelf.recodeValuation()
newOther = deepcopy(other)
newOther.recodeValuation()
Min = newSelf.valuationDomain['min']
Max = newSelf.valuationDomain['max']
res = Max
for it in newSelf.support:
print(it)
try:
resit = -( min(newSelf.membership[it],-(newOther.membership[it])) )
print(res,newSelf.membership[it],newOther.membership[it],resit)
except:
newOther.membership[it] = Min
resit = -( min(newSelf.membership[it],-(newOther.membership[it])) )
print(res,newSelf.membership[it],newOther.membership[it],resit)
if resit < res:
res = resit
return res
def __sub__(self,other,/):
"""
Return difference self - other
"""
return self.difference(other)
[docs]
def difference(self,other,/):
"""
Return the set difference between self and other
"""
from copy import deepcopy
from bipolarValuedSets import BpvSet
newSelf = deepcopy(self)
newSelf.recodeValuation()
newOther = deepcopy(other)
newOther.recodeValuation()
diff = BpvSet()
diff.name = self.name+'-'+other.name
# union of the supports
for it in newSelf.support:
#print(it)
if it not in diff.support:
diff.support[it] = newSelf.support[it]
for it in newOther.support:
#print(it)
if it not in diff.support:
diff.support[it] = newOther.support[it]
membership = {}
Min = diff.valuationDomain['min']
Med = diff.valuationDomain['med']
Max = diff.valuationDomain['max']
for it in diff.support:
try:
membership[it] = min(newSelf.membership[it],-newOther.membership[it])
except:
try:
membership[it] = newSelf.membership[it]
except:
membership[it] = Min
diff.ndigits = min(newSelf.ndigits, newOther.ndigits)
diff.membership = membership
diff.determinateness = diff.computeDeterminateness()
diff.cardinality = diff.computeCardinality()
return diff
[docs]
def oplus(self,other,/):
"""
Returns the disjunctive fusion of self and other
"""
from copy import deepcopy
from bipolarValuedSets import BpvSet
newSelf = deepcopy(self)
newSelf.recodeValuation()
newOther = deepcopy(other)
newOther.recodeValuation()
fusion = BpvSet()
fusion.name = self.name+'Oplus'+other.name
# union of the supports
for it in newSelf.support:
#print(it)
if it not in fusion.support:
fusion.support[it] = newSelf.support[it]
for it in newOther.support:
#print(it)
if it not in fusion.support:
fusion.support[it] = newOther.support[it]
membership = {}
Min = fusion.valuationDomain['min']
Med = fusion.valuationDomain['med']
Max = fusion.valuationDomain['max']
for it in fusion.support:
try:
if newSelf.membership[it] <= Med and newOther.membership[it] <= Med:
membership[it] = min(newSelf.membership[it],newOther.membership[it])
elif newSelf.membership[it] >= Med and newOther.membership[it] >= Med:
membership[it] = max(newSelf.membership[it],newOther.membership[it])
else:
membership[it] = Med
except:
try:
membership[it] = newSelf.membership[it]
except:
membership[it] = Min
fusion.ndigits = min(newSelf.ndigits, newOther.ndigits)
fusion.membership = membership
fusion.determinateness = fusion.computeDeterminateness()
fusion.cardinality = fusion.computeCardinality()
return fusion
[docs]
def otimes(self,other,/):
"""
Returns the disjunctive fusion of self and other
"""
from copy import deepcopy
from bipolarValuedSets import BpvSet
newSelf = deepcopy(self)
newSelf.recodeValuation()
newOther = deepcopy(other)
newOther.recodeValuation()
fusion = BpvSet()
fusion.name = self.name+'Oplus'+other.name
# union of the supports
for it in newSelf.support:
#print(it)
if it not in fusion.support:
fusion.support[it] = newSelf.support[it]
for it in newOther.support:
#print(it)
if it not in fusion.support:
fusion.support[it] = newOther.support[it]
membership = {}
Min = fusion.valuationDomain['min']
Med = fusion.valuationDomain['med']
Max = fusion.valuationDomain['max']
for it in fusion.support:
try:
if newSelf.membership[it] <= Med and newOther.membership[it] <= Med:
membership[it] = max(newSelf.membership[it],newOther.membership[it])
elif newSelf.membership[it] >= Med and newOther.membership[it] >= Med:
membership[it] = min(newSelf.membership[it],newOther.membership[it])
else:
membership[it] = Med
except:
try:
membership[it] = newSelf.membership[it]
except:
membership[it] = Min
fusion.ndigits = min(newSelf.ndigits, newOther.ndigits)
fusion.membership = membership
fusion.determinateness = fusion.computeDeterminateness()
fusion.cardinality = fusion.computeCardinality()
return fusion
def __neg__(self,/):
"""
Return -self
"""
return self.dual()
[docs]
def dual(self,/):
"""
Return the complement or dual of self wrt self.support
"""
from copy import deepcopy
from bipolarValuedSets import BpvSet
#newSelf = deepcopy(self)
#newSelf.recodeValuation()
comp = BpvSet()
comp.name = self.name+'comp'
comp.support = self.support
comp.valuationDomain = self.valuationDomain
membership = {}
for it in comp.support:
membership[it] = -self.membership[it]
comp.ndigits = self.ndigits
comp.membership = membership
comp.determinateness = comp.computeDeterminateness()
comp.cardinality = comp.computeCardinality()
return comp
def __xor__(self,other,/):
"""
Return symmetric difference self^other
"""
return ((self - other) | (other - self))
[docs]
def update(self,other,/):
"""
Return the union of X and Y
"""
return self.union(other)
[docs]
def strip(self,cutLevel=None,Strict=True,InSite=True):
"""
Strip the support at *cutLevel*.
If None, *cutLevel* is set to the valuation minimum and *Strict* is forced to True.
If Strict is True, cutLevel is stripped !
"""
if cutLevel is None:
cutLevel = self.valuationDomain['min']
Strict = True
new = BpvSet()
new.name = self.name
new.valuationDomain = self.valuationDomain
support = self.support
membership = self.membership
for it in support:
if Strict:
if membership[it] > cutLevel:
new.support[it] = support[it]
new.membership[it] = membership[it]
else:
if membership[it] >= cutLevel:
new.support[it] = support[it]
new.membership[it] = membership[it]
new.ndigits = self.ndigits
if InSite:
self.support = new.support
self.membership = new.membership
self.cardinality = new.computeCardinality()
self.determinateness = new.computeDeterminateness()
return self
else:
new.cardinality = new.computeCardinality()
new.determinateness = new.computeDeterminateness()
return new
[docs]
def polarise(self,cutLevel=None,Strict=True,InSite=True):
"""
Polarize self at cutLevel. If None, cutLevel is 0.0.
If Strict is True, all valuations > cutLevel are set to +1.0 and
all valuations < -cutLevel are set to -1.0.
All valuation in the range [cutLevel;-cutLevel] are set to 0.0 !
If cutLevel = 0.0, the polarisation is always strict !
"""
if cutLevel is None:
cutLevel = self.valuationDomain['med']
Strict = True
new = BpvSet()
new.name = self.name
new.valuationDomain = self.valuationDomain
Min = new.valuationDomain['min']
Med = new.valuationDomain['med']
Max = new.valuationDomain['max']
new.support = self.support
membership = self.membership
for it in new.support:
if Strict:
if membership[it] > cutLevel:
new.membership[it] = Max
elif membership[it] < -cutLevel:
new.membership[it] = Min
else:
new.membership[it] = Med
else:
if membership[it] >= cutLevel:
new.membership[it] = Max
elif membership[it] <= -cutLevel:
new.membership[it] = Min
else:
new.membership[it] = Med
new.ndigits = self.ndigits
if InSite:
self.membership = new.membership
self.cardinality = new.computeCardinality()
self.determinateness = new.computeDeterminateness()
return self
else:
new.cardinality = new.computeCardinality()
new.determinateness = new.computeDeterminateness()
return new
#-----------------
[docs]
class RandomBpvSet(BpvSet):
"""
*undeterminateness* parameter (in float % [0.0,1.0], default=0.1)
"""
def __init__(self,numberOfElements=5,
elementNamePrefix='x',
undeterminateness=0.1,
valuationRange=(-1,1),
ndigits = 4,
seed=None,
Debug=False):
# store arguments
self.cardinality = numberOfElements
self.elementNamePrefix = 'elementNamePrefix'
self.randomSeed = seed
self.ndigits = ndigits
# set random seed (none by default)
import random
random.seed(seed)
# setting object name
self.name = 'randomBpvSet'
# generate random items
nd = len(str(numberOfElements))
support = {}
for i in range(1,numberOfElements+1):
elementKey = ('%s%%0%dd' % (elementNamePrefix,nd)) % (i)
support[elementKey] = {'shortName':elementKey,
'name': 'potential element #%d' % i,
'comment': 'RandomBpvset() generated.' }
self.support = support
# setting valuation domain
valuationDomain = {'min': Decimal(str(valuationRange[0])),
'med': Decimal(0),
'max': Decimal(str(valuationRange[1]))}
Max = valuationDomain['max']
Med = valuationDomain['med']
Min = valuationDomain['min']
self.valuationDomain = valuationDomain
# setting characetistc values
membership = dict()
formatString = '%%.%df' % ndigits
#print(formatString)
for it in support:
u = (random.random() * 2.0) - 1.0
membership[it] = Decimal(formatString % u) * Max
if Debug:
print(it,u,membership[it])
if u > -undeterminateness and u < undeterminateness:
membership[it] = Med
if Debug:
print(it,u,membership[it])
self.membership = membership
# setting bpvSet dimension
cardinality = 0
determinateness = Decimal(0)
for it in support:
if membership[it] > Med:
cardinality += 1
self.cardinality = cardinality
self.determinateness = self.computeDeterminateness()
#############################################
# scratch space for testing ongoing developments
#----------test the module ----------------
if __name__ == "__main__":
print('*****************************************************')
print('* bipolarValuedSets.py module *')
print('* $Revision: Python3.13$ *')
print('* Copyright (C) 2025 Raymond Bisdorff *')
print('* The module comes with ABSOLUTELY NO WARRANTY *')
print('* to the extent permitted by the applicable law. *')
print('* This is free software, and you are welcome to *')
print('* redistribute it if it remains free software. *')
print('*****************************************************')
print('*-------- Testing classes and methods -------')
X = RandomBpvSet(numberOfElements=5,elementNamePrefix='s',
undeterminateness=0.1,
valuationRange=(-1,1),ndigits=4,
seed=1,
Debug=False)
#X.showMembershipCharacteristics(Normalized=False)
X.showMembershipCharacteristics()
Y = RandomBpvSet(numberOfElements=5,elementNamePrefix='s',
undeterminateness=0.1,
valuationRange=(-1,1),
seed=2,ndigits=4,
Debug=False)
#Y.showMembershipCharacteristics(Normalized=False)
Y.showMembershipCharacteristics()
D = Y - X
E = D.strip(InSite=False)
D.strip()
Op = X.oplus(Y)
Op.showMembershipCharacteristics()
Om = X.otimes(Y)
Om.showMembershipCharacteristics()
M = RandomBpvSet(undeterminateness=1.0,elementNamePrefix='s')
Oxmp = X.oplus(M)
Oxmp.showMembershipCharacteristics()
Oxmm = X.otimes(M)
Oxmm.showMembershipCharacteristics()