Repository: idaholab/SR2ML (language: python)

Issue:
NLP for word and sentence similarity calculation 
--------
Issue Description
--------

Compute word similarity based on the length and depth in wordnet. 
Compute sentence similarity based semantic similarity, word order similarity, and word disambiguation analysis. 

**Is your feature request related to a problem? Please describe.**


**Describe the solution you'd like**


**Describe alternatives you've considered**


**Additional context**

----------------
For Change Control Board: Issue Review
----------------
This review should occur before any development is performed as a response to this issue.
- [ ] 1. Is it tagged with a type: defect or task?
- [ ] 2. Is it tagged with a priority: critical, normal or minor?
- [ ] 3. If it will impact requirements or requirements tests, is it tagged with requirements?
- [ ] 4. If it is a defect, can it cause wrong results for users? If so an email needs to be sent to the users.
- [ ] 5. Is a rationale provided? (Such as explaining why the improvement is needed or why current code is wrong.)

-------
For Change Control Board: Issue Closure
-------
This review should occur when the issue is imminently going to be closed.
- [ ] 1. If the issue is a defect, is the defect fixed?
- [ ] 2. If the issue is a defect, is the defect tested for in the regression test system?  (If not explain why not.)
- [ ] 3. If the issue can impact users, has an email to the users group been written (the email should specify if the defect impacts stable or master)?
- [ ] 4. If the issue is a defect, does it impact the latest release branch? If yes, is there any issue tagged with release (create if needed)?
- [ ] 5. If the issue is being closed without a pull request, has an explanation of why it is being closed been provided?



Discussion / hints:

Fix patch:
diff --git a/dependencies.xml b/dependencies.xml
index a6bdcc6..3c33d44 100644
--- a/dependencies.xml
+++ b/dependencies.xml
@@ -14,6 +14,9 @@
     <numerizer source='pip' optional='True'/>
     <autocorrect source='pip' optional='True'/>
     <contextualSpellCheck source='pip' optional='True'/>
+    <numpy/>
+    <pywsd source='pip'/>
+    <jupyterlab source='pip'/>
     <!-- <recognizers-text-number source='pip'>
     <recognizers-text-number-with-unit source='pip'>
     <recognizers-text-date-time source='pip'> -->
diff --git a/src/nlp/similarity/Readme.md b/src/nlp/similarity/Readme.md
new file mode 100644
index 0000000..02c023b
--- /dev/null
+++ b/src/nlp/similarity/Readme.md
@@ -0,0 +1,6 @@
+### Required libraries:
+numpy, pandas, nltk, pywsd, jupyter
+
+conda create -n similarity_libs -c  conda-forge numpy pandas nltk
+conda activate similarity_libs
+pip install -U pywsd
diff --git a/src/nlp/similarity/SentenceSimilarity.py b/src/nlp/similarity/SentenceSimilarity.py
new file mode 100644
index 0000000..b75a08b
--- /dev/null
+++ b/src/nlp/similarity/SentenceSimilarity.py
@@ -0,0 +1,131 @@
+# modified from https://github.com/nihitsaxena95/sentence-similarity-wordnet-sementic/blob/master/SentenceSimilarity.py
+# Method proposed by: https://arxiv.org/pdf/1802.05667.pdf
+
+import nltk
+from pywsd.lesk import simple_lesk
+import numpy as np
+from nltk.corpus import wordnet, wordnet_ic
+import simUtils
+
+import logging
+log_format = "%(asctime)s %(message)s"
+logging.basicConfig(
+    filename="sentence_similarity_computing.log",
+    filemode="a",
+    level=logging.INFO,
+    format=log_format,
+    datefmt="%m/%d %I:%M:%S %p",
+)
+logger = logging.getLogger("SentenceSimilarity")
+
+class SentenceSimilarity:
+
+  def __init__(self, disambiguationMethod='simple_lesk', similarityMethod='semantic_similarity_synsets', wordOrderContribution=0.0):
+    """
+      Options for the initiation for this class
+    """
+    self.validDisambiguation = ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']
+    self.wordnetSimMethod = ["path_similarity", "wup_similarity", "lch_similarity", "res_similarity", "jcn_similarity", "lin_similarity"]
+    self.validSimilarity = self.wordnetSimMethod + ["semantic_similarity_synsets"]
+    self.wordOrder = wordOrderContribution
+    if disambiguationMethod.lower() not in self.validDisambiguation:
+      raise ValueError(f'Inappropriate argument value for "disambiguationMethod", valid values are {self.validDisambiguation}')
+    if similarityMethod.lower() not in self.validSimilarity:
+      raise ValueError(f'Inappropriate argument value for "similarityMethod", valid values are {self.validSimilarity}')
+    self.disambiguationMethod = disambiguationMethod.lower()
+    self.similarityMethod = similarityMethod.lower()
+    self.brownIc = wordnet_ic.ic('ic-brown.dat')
+
+  def setParameters(self, paramDict):
+    """
+      Method to set the parameters
+    """
+    for key, value in paramDict:
+      if key in self.__dict__:
+        setattr(self, key, value)
+
+  def constructSimilarityVectorPawarMagoMethod(self, arr1, arr2):
+    """
+      Construct the similarity vector
+      @ In, arr1, set of wordnet.Synset for one sentence
+      @ In, arr2, set of wordnet.Synset for the other sentence
+      @ Out, vector, list, list of similarity vector
+      @ Out, count, int, the number of words that have high similarity >=0.804
+    """
+    # vectorLen, the large length between arr1 and arr2
+    if len(arr1) >= len(arr2):
+      vectorLen = len(arr1)
+    else:
+      vectorLen = len(arr2)
+    vector = [0.0] * vectorLen
+    count = 0
+    for i,a1 in enumerate(arr1):
+      allSimilarityIndex=[]
+      for a2 in arr2:
+        if a1 is None or a2 is None:
+          similarity = 0.
+        else:
+          a1Edit = wordnet.synset(a1.name())
+          a2Edit = wordnet.synset(a2.name())
+          similarity = simUtils.synsetsSimilarity(a1Edit, a2Edit, method=self.similarityMethod)
+        if similarity != None:
+          allSimilarityIndex.append(similarity)
+        else:
+          allSimilarityIndex.append(0.0)
+      allSimilarityIndex = sorted(allSimilarityIndex, reverse = True)
+      vector[i]=allSimilarityIndex[0]
+      # According to Rubinstein 1965, the benchmark synonymy value of two word is 0.8025.
+      if vector[i] >= 0.8025:
+        count +=1
+    vector = np.asarray(vector)
+    return vector, count
+
+  def sentenceSimilarity(self, sentence1, sentence2, method='pm_disambiguation', infoContentNorm=False):
+    """
+      sentence similarity calculation
+    """
+    if method.lower() == 'pm_disambiguation':
+      similarity = self.sentenceSimilarityPawarMagoMethod(sentence1, sentence2)
+    elif method.lower() == 'best_sense':
+      similarity = self.sentenceSimialrityBestSense(sentence1, sentence2, infoContentNorm)
+    else:
+      raise ValueError(f'{method} is not a valid option, please try "pm_disambiguation" or "best_sense"')
+    return similarity
+
+  def sentenceSimilarityPawarMagoMethod(self, sentence1, sentence2):
+    """
+      Proposed method from https://arxiv.org/pdf/1802.05667.pdf
+      @ In, sentence1, str, first sentence used to compute sentence similarity
+      @ In, sentence2, str, second sentence used to compute sentence similarity
+      @ Out, similarity, float, [0, 1], the computed similarity for given two sentences
+    """
+    _, sense1 = simUtils.sentenceSenseDisambiguationPyWSD(sentence1, senseMethod=self.disambiguationMethod, simMethod='path')
+    _, sense2 = simUtils.sentenceSenseDisambiguationPyWSD(sentence2, senseMethod=self.disambiguationMethod, simMethod='path')
+    v1, c1 = self.constructSimilarityVectorPawarMagoMethod(sense1,sense2)
+    v2, c2 = self.constructSimilarityVectorPawarMagoMethod(sense2,sense1)
+    # FIXME: check the following algorithms with benchmarks
+    # dot = np.dot(v1,v2)
+    # from original paper
+    dot = np.linalg.norm(v1)*np.linalg.norm(v2)
+    # print("dot", dot) # getting the dot product
+    tow = (c1+c2)/1.8
+    if tow == 0.:
+      tow = len(v1)/2.0
+    semanticSimilarity = dot/tow
+    # print("similarity",semanticSimilarity)
+    similarity = (1-self.wordOrder) * semanticSimilarity + self.wordOrder * simUtils.wordOrderSimilaritySentences(sentence1, sentence2)
+    return similarity
+
+
+  def sentenceSimialrityBestSense(self, sentence1, sentence2, infoContentNorm=False):
+    """
+      Proposed method from https://github.com/anishvarsha/Sentence-Similaritity-using-corpus-statistics
+      Compute sentence similarity using both semantic and word order similarity
+      The semantic similarity is based on maximum word similarity between one word and another sentence
+      @ In, sentence1, str, first sentence used to compute sentence similarity
+      @ In, sentence2, str, second sentence used to compute sentence similarity
+      @ In, infoContentNorm, bool, True if statistics corpus is used to weight similarity vectors
+      @ Out, similarity, float, [0, 1], the computed similarity for given two sentences
+    """
+    similarity = (1-self.wordOrder) * simUtils.semanticSimilaritySentences(sentence1, sentence2, infoContentNorm) + self.wordOrder * simUtils.wordOrderSimilaritySentences(sentence1, sentence2)
+    return similarity
diff --git a/src/nlp/similarity/__init__.py b/src/nlp/similarity/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/src/nlp/similarity/__init__.py
@@ -0,0 +1,1 @@
+#
diff --git a/src/nlp/similarity/benchmark/Li2006_sim_sents.csv b/src/nlp/similarity/benchmark/Li2006_sim_sents.csv
new file mode 100644
index 0000000..b1bdaff
--- /dev/null
+++ b/src/nlp/similarity/benchmark/Li2006_sim_sents.csv
@@ -0,0 +1,66 @@
+﻿No,sent1,sent2,MeanHumanSimilarity,PV2018
+1,"Cord is strong, thick string.","A smile is the expression that you have on your face when you are pleased or amused, or when you are being friendly.",0.01,0.0225
+2,A rooster is an adult male chicken. ,A voyage is a long journey on a ship or in a spacecraft.,0.005,0.2593
+3,Noon is 12 o’clock in the middle of the day.,"String is thin rope made of twisted threads, used for tying things together or tying up parcels.",0.0125,0.03455
+4,Fruit or a fruit is something which grows on a tree or bush and which contains seeds or a stone covered by a substance that you can eat.,"A furnace is a container or enclosed space in which a very hot fire is made, for example to melt metal, burn rubbish or produce steam.",0.0476,0.1388
+5,An autograph is the signature of someone famous which is specially written for a fan to keep.,"The shores or shore of a sea, lake, or wide river is the land along the edge of it.",0.005,0.0701
+6,An automobile is a car. ,"In legends and fairy stories, a wizard is a man who has magic powers.",0.02,0.0088
+7,A mound of something is a large rounded pile of it.,"A stove is a piece of equipment which provides heat, either for cooking or for heating a room.",0.005,0.4968
+8,A grin is a broad smile.,An implement is a tool or other piece of equipment.,0.005,0.0099
+9,An asylum is a psychiatric hospital.,Fruit or a fruit is something which grows on a tree or bush and which contains seeds or a stone covered by a substance that you can eat.,0.005,0.01456
+10,An asylum is a psychiatric hospital.,A monk is a member of a male religious community that is usually separated from the outside world.,0.0375,0.0175
+11,"A graveyard is an area of land, sometimes near a church, where dead people are buried.","If you describe a place or situation as a madhouse, you mean that it is full of confusion and noise.",0.0225,0.1339
+12,Glass is a hard transparent substance that is used to make things such as windows and bottles. ,A magician is a person who entertains people by doing magic tricks.,0.0075,0.0911
+13,A boy is a child who will grow up to be a man.,A rooster is an adult male chicken. ,0.1075,0.2921
+14,"A cushion is a fabric case filled with soft material, which you put on a seat to make it more comfortable.","A jewel is a precious stone used to decorate valuable things that you wear, such as rings or necklaces.",0.0525,0.1745
+15,A monk is a member of a male religious community that is usually separated from the outside world.,A slave is someone who is the property of another person and has to work for that person.,0.045,0.1394
+16,An asylum is a psychiatric hospital.,A cemetery is a place where dead peoples bodies or their ashes are buried.,0.375,0.003398
+17,The coast is an area of land that is next to the sea.,A forest is a large area where trees grow close together.,0.0475,0.3658
+18,A grin is a broad smile.,A lad is a young man or boy.,0.0125,0.0281
+19,"The shores or shore of a sea, lake, or wide river is the land along the edge of it.",Woodland is land with a lot of trees.,0.0825,0.3192
+20,A monk is a member of a male religious community that is usually separated from the outside world.,"In ancient times, an oracle was a priest or priestess who made statements about future events or about the truth.",0.1125,0.1011
+21,A boy is a child who will grow up to be a man.,A sage is a person who is regarded as being very wise.,0.0425,0.2305
+22,An automobile is a car. ,"A cushion is a fabric case filled with soft material, which you put on a seat to make it more comfortable.",0.02,0.033
+23,A mound of something is a large rounded pile of it.,"The shores or shore of a sea, lake, or wide river is the land along the edge of it.",0.035,0.0386
+24,A lad is a young man or boy.,"In legends and fairy stories, a wizard is a man who has magic powers.",0.0325,0.3939
+25,A forest is a large area where trees grow close together.,"A graveyard is an area of land, sometimes near a church, where dead people are buried.",0.065,0.2787
+26,Food is what people and animals eat.,A rooster is an adult male chicken. ,0.055,0.2972
+27,A cemetery is a place where dead peoples bodies or their ashes are buried.,Woodland is land with a lot of trees.,0.0375,0.124
+28,"The shores or shore of a sea, lake, or wide river is the land along the edge of it.",A voyage is a long journey on a ship or in a spacecraft.,0.02,0.0304
+29,"A bird is a creature with feathers and wings, females lay eggs, and most birds can fly.",Woodland is land with a lot of trees.,0.0125,0.1334
+30,The coast is an area of land that is next to the sea.,A hill is an area of land that is higher than the land that surrounds it.,0.1,0.8032
+31,"A furnace is a container or enclosed space in which a very hot fire is made, for example to melt metal, burn rubbish or produce steam.",An implement is a tool or other piece of equipment.,0.05,0.1408
+32,A crane is a large machine that moves heavy things by lifting them in the air.,A rooster is an adult male chicken.,0.02,0.0564
+33,A hill is an area of land that is higher than the land that surrounds it.,Woodland is land with a lot of trees.,0.145,0.7619
+34,A car is a motor vehicle with room for a small number of passengers.,"When you make a journey, you travel from one place to another.",0.0725,0.0261
+35,A cemetery is a place where dead peoples bodies or their ashes are buried.,A mound of something is a large rounded pile of it.,0.0575,0.0842
+36,Glass is a hard transparent substance that is used to make things such as windows and bottles.,"A jewel is a precious stone used to decorate valuable things that you wear, such as rings or necklaces.",0.1075,0.2692
+37,A magician is a person who entertains people by doing magic tricks.,"In ancient times, an oracle was a priest or priestess who made statements about future events or about the truth.",0.13,0.1
+38,A crane is a large machine that moves heavy things by lifting them in the air.,An implement is a tool or other piece of equipment.,0.185,0.106
+39,Your brother is a boy or a man who has the same parents as you.,A lad is a young man or boy.,0.1275,0.9515
+40,A sage is a person who is regarded as being very wise.,"In legends and fairy stories, a wizard is a man who has magic powers.",0.1525,0.192
+41,"In ancient times, an oracle was a priest or priestess who made statements about future events or about the truth.",A sage is a person who is regarded as being very wise.,0.2825,0.0452
+42,"A bird is a creature with feathers and wings, females lay eggs, and most birds can fly." ,A crane is a large machine that moves heavy things by lifting them in the air.,0.035,0.166
+43,"A bird is a creature with feathers and wings, females lay eggs, and most birds can fly." ,A cock is an adult male chicken.,0.1625,0.1704
+44,Food is what people and animals eat.,Fruit or a fruit is something which grows on a tree or bush and which contains seeds or a stone covered by a substance that you can eat.,0.2425,0.1379
+45,Your brother is a boy or a man who has the same parents as you.,A monk is a member of a male religious community that is usually separated from the outside world.,0.045,0.278
+46,An asylum is a psychiatric hospital.,"If you describe a place or situation as a madhouse, you mean that it is full of confusion and noise.",0.215,0.186
+47,"A furnace is a container or enclosed space in which a very hot fire is made, for example to melt metal, burn rubbish or produce steam.","A stove is a piece of equipment which provides heat, either for cooking or for heating a room.",0.3475,0.1613
+48,A magician is a person who entertains people by doing magic tricks.,"In legends and fairy stories, a wizard is a man who has magic powers.",0.355,0.5399
+49,A hill is an area of land that is higher than the land that surrounds it.,A mound of something is a large rounded pile of it.,0.2925,0.2986
+50,"Cord is strong, thick string.","String is thin rope made of twisted threads, used for tying things together or tying up parcels." ,0.477,0.253
+51,Glass is a hard transparent substance that is used to make things such as windows and bottles. ,A tumbler is a drinking glass with straight sides.,0.1575,0.3016
+52,A grin is a broad smile.,"A smile is the expression that you have on your face when you are pleased or amused, or when you are being friendly.",0.485,0.8419
+53,"In former times, serfs were a class of people who had to work on a particular persons land and could not leave without that persons permission.",A slave is someone who is the property of another person and has to work for that person.,0.4825,0.8896
+54,"When you make a journey, you travel from one place to another.",A voyage is a long journey on a ship or in a spacecraft.,0.36,0.7826
+55,An autograph is the signature of someone famous which is specially written for a fan to keep.,"Your signature is your name, written in your own characteristic way, often at the end of a document to indicate that you wrote the document or that you agree with what it says.",0.405,0.3146
+56,The coast is an area of land that is next to the sea.,"The shores or shore of a sea, lake, or wide river is the land along the edge of it." ,0.5875,0.9773
+57,A forest is a large area where trees grow close together.,Woodland is land with a lot of trees.,0.6275,0.477
+58,An implement is a tool or other piece of equipment.,A tool is any instrument or simple piece of equipment that you hold in your hands and use to do a particular kind of work.,0.59,0.8919
+59,A cock is an adult male chicken.,A rooster is an adult male chicken. ,0.8625,0.856
+60,A boy is a child who will grow up to be a man.,A lad is a young man or boy.,0.58,0.898
+61,"A cushion is a fabric case filled with soft material, which you put on a seat to make it more comfortable.",A pillow is a rectangular cushion which you rest your head on when you are in bed.,0.5225,0.934
+62,A cemetery is a place where dead peoples bodies or their ashes are buried.,"A graveyard is an area of land, sometimes near a church, where dead people are buried.",0.7725,1
+63,An automobile is a car.,A car is a motor vehicle with room for a small number of passengers.,0.5575,0.7001
+64,Midday is 12 oclock in the middle of the day.,Noon is 12 oclock in the middle of the day.,0.955,0.8726
+65,A gem is a jewel or stone that is used in jewellery,"A jewel is a precious stone used to decorate valuable things that you wear, such as rings or necklaces.",0.6525,0.8536
diff --git a/src/nlp/similarity/benchmark/noun_pairs.csv b/src/nlp/similarity/benchmark/noun_pairs.csv
new file mode 100644
index 0000000..743bad5
--- /dev/null
+++ b/src/nlp/similarity/benchmark/noun_pairs.csv
@@ -0,0 +1,66 @@
+﻿No,word1,word2,RG_similarity,Lee2014,PV2018
+1,cord,smile,0.005,0.01,0.089902168
+2,noon,string,0.01,0.005,0.044040149
+3,rooster,voyage,0.01,0.0125,0.010051669
+4,fruit,furnace,0.0125,0.0475,0.072044464
+5,autograph,shore,0.015,0.005,0.074255248
+6,automobile,wizard,0.0275,0.02,0.090695565
+7,mound,stove,0.035,0.005,0.065641991
+8,grin,implement,0.045,0.005,0.089902168
+9,asylum,fruit,0.0475,0.005,0.072044464
+10,asylum,monk,0.0975,0.0375,0.075728976
+11,graveyard,madhouse,0.105,0.0225,0.060795055
+12,boy,rooster,0.11,0.0075,0.090716449
+13,glass,magician,0.11,0.1075,0.178214441
+14,cushion,jewel,0.1125,0.0525,0.244379429
+15,monk,slave,0.1425,0.045,0.375088075
+16,asylum,cemetery,0.1975,0.0375,0.110637834
+17,coast,forest,0.2125,0.0475,0.110637834
+18,grin,lad,0.22,0.0125,0.089902168
+19,shore,woodland,0.225,0.0825,0.30111988
+20,monk,oracle,0.2275,0.1125,0.246447306
+21,boy,sage,0.24,0.0425,0.201773988
+22,automobile,cushion,0.2425,0.02,0.201846692
+23,mound,shore,0.2425,0.035,0.201846692
+24,lad,wizard,0.2475,0.0325,0.367330544
+25,forest,graveyard,0.25,0.065,0.201595277
+26,food,rooster,0.2725,0.055,0.273232692
+27,cemetery,woodland,0.295,0.0375,0.201595277
+28,shore,voyage,0.305,0.02,0.407521443
+29,bird,woodland,0.31,0.0125,0.165198569
+30,coast,hill,0.315,0.1,0.410361732
+31,furnace,implement,0.3425,0.05,0.246447306
+32,crane,rooster,0.3525,0.02,0.246592874
+33,hill,woodland,0.37,0.145,0.291842139
+34,car,journey,0.3875,0.0725,0.273071398
+35,cemetery,mound,0.4225,0.0575,0.065641991
+36,glass,jewel,0.445,0.1075,0.31767161
+37,magician,oracle,0.455,0.13,0.305740363
+38,crane,implement,0.5925,0.185,0.448658539
+39,brother,lad,0.6025,0.1275,0.546229027
+40,sage,wizard,0.615,0.1525,0.367511562
+41,oracle,sage,0.6525,0.2825,0.527930733
+42,bird,cock,0.6575,0.035,0.575083881
+43,bird,crane,0.67,0.1625,0.497850372
+44,food,fruit,0.6725,0.2425,0.619607505
+45,brother,monk,0.685,0.045,0.266457136
+46,asylum,madhouse,0.76,0.215,0.818528699
+47,furnace,stove,0.7775,0.3475,0.165198569
+48,magician,wizard,0.8025,0.355,0.998507942
+49,hill,mound,0.8225,0.2925,0.814801075
+50,cord,string,0.8525,0.47,0.814801075
+51,glass,tumbler,0.8625,0.1375,0.856140254
+52,grin,smile,0.865,0.485,0.991007454
+53,serf,slave,0.865,0.4825,0.867330544
+54,journey,voyage,0.895,0.36,0.818528699
+55,autograph,signature,0.8975,0.405,0.849945707
+56,coast,shore,0.9,0.5875,0.817912022
+57,forest,woodland,0.9125,0.6275,0.978026115
+58,implement,tool,0.915,0.59,0.082291949
+59,cock,rooster,0.92,0.8625,0.909350292
+60,boy,lad,0.955,0.58,0.909350292
+61,cushion,pillow,0.96,0.5225,0.815729386
+62,cemetery,graveyard,0.97,0.7725,0.998507942
+63,automobile,car,0.98,0.5575,0.818528699
+64,gem,jewel,0.985,0.955,0.81750916
+65,midday,noon,0.985,0.625,0.999393106
diff --git a/src/nlp/similarity/main.py b/src/nlp/similarity/main.py
new file mode 100644
index 0000000..340183c
--- /dev/null
+++ b/src/nlp/similarity/main.py
@@ -0,0 +1,139 @@
+import simUtils
+from SentenceSimilarity import SentenceSimilarity
+import pandas as pd
+import os
+
+currentPath = os.path.dirname(os.path.abspath(__file__))
+wordPairsFile = os.path.join(currentPath, "benchmark", "noun_pairs.csv")
+sentencePairsFile = os.path.join(currentPath, "benchmark", "Li2006_sim_sents.csv")
+
+fromFile = True
+if fromFile:
+  wordPairsData = pd.read_csv(wordPairsFile,  header=0)
+  sentencePairsData = pd.read_csv(sentencePairsFile, header=0)
+else:
+  # Results from https://arxiv.org/pdf/1802.05667.pdf
+  # with wordOrder = 0.15, word order threshold: 0.4, semantic threshold: 0.2
+  wordPairs = [
+      ["asylum", "fruit", 0.21],
+      ["autograph", "shore", 0.29],
+      ["autograph", "signature", 0.55],
+      ["automobile", "car", 0.64],
+      ["bird", "woodland", 0.33],
+      ["boy", "rooster", 0.53],
+      ["boy", "lad", 0.66],
+      ["boy", "sage", 0.51],
+      ["cemetery", "graveyard", 0.73],
+      ["coast", "forest", 0.36],
+      ["coast", "shore", 0.76],
+      ["cock", "rooster", 1.00],
+      ["cord", "smile", 0.33],
+      ["cord", "string", 0.68],
+      ["cushion", "pillow", 0.66],
+      ["forest", "graveyard", 0.55],
+      ["forest", "woodland", 0.70],
+      ["furnace", "stove", 0.72],
+      ["glass", "tumbler", 0.65],
+      ["grin", "smile", 0.49],
+      ["gem", "jewel", 0.83],
+      ["hill", "woodland", 0.59],
+      ["hill", "mound", 0.74],
+      ["implement", "tool", 0.75],
+      ["journey", "voyage", 0.52],
+      ["magician", "oracle", 0.44],
+      ["magician", "wizard", 0.65],
+      ["midday", "noon", 1.0],
+      ["oracle", "sage", 0.43],
+      ["serf", "slave", 0.39]
+  ]
+  wordPairsData = pd.DataFrame(wordPairs, columns=['word1', 'word2', 'similarity'])
+
+  sentencePairs = [
+      ["I like that bachelor.", "I like that unmarried man.", 0.561],
+      ["Red alcoholic drink.", "A bottle of wine.", 0.585],
+      ["I have a hammer.", "Take some nails.", 0.508],
+      ["John is very nice.", "Is John very nice?", 0.977],
+      ["It is a dog.", "It is a log.", 0.623],
+      ["Red alcoholic drink.", "An English dictionary.", 0.0],
+      ["Dogs are animals.", "They are common pets.", 0.738],
+      ["Canis familiaris are animals.", "Dogs are common pets.", 0.362],
+      ["A glass of cider.", "A full cup of apple juice.", 0.678],
+      ["I have a hammer.", "Take some apples.", 0.121],
+      ["It is a dog.", "It is a pig.", 0.790],
+      ["I have a pen.", "Where is ink?", 0.129],
+      ["I have a pen.", "Where do you live?", 0.0],
+      ["Red alcoholic drink.", "Fresh orange juice.", 0.611],
+      ["Red alcoholic drink.", "Fresh apple juice.", 0.420],
+      ["It is a dog.", "That must be your dog.", 0.739],
+  ]
+  sentencePairsData = pd.DataFrame(sentencePairs, columns=['sent1', 'sent2', 'similarity'])
+
+if __name__ == '__main__':
+
+  wordSimSemantic = []
+  print("Words Similairty using semanticSimilarityWords:")
+  for index, wordPair in wordPairsData.iterrows():
+    calculated = simUtils.semanticSimilarityWords(wordPair['word1'], wordPair['word2'])
+    wordSimSemantic.append(calculated)
+    print(" ".join([str(e)+'\t' for e in wordPair.to_numpy()]), calculated)
+
+  print("Words Similairty using wordsSimilarity semantic_similarity_synsets method:")
+  for index, wordPair in wordPairsData.iterrows():
+    calculated = simUtils.wordsSimilarity(wordPair['word1'], wordPair['word2'], method='semantic_similarity_synsets')
+    print(" ".join([str(e)+'\t' for e in wordPair.to_numpy()]), calculated)
+
+  wordSimPath = []
+  print("Words Similairty using wordsSimilarity wordnet path method:")
+  for index, wordPair in wordPairsData.iterrows():
+    calculated = simUtils.wordsSimilarity(wordPair['word1'], wordPair['word2'], method='path')
+    wordSimPath.append(calculated)
+    print(" ".join([str(e)+'\t' for e in wordPair.to_numpy()]), calculated)
+
+  senSimBestSense = []
+  print("Sentence Similarity Best Sense:")
+  for index, sentPair in sentencePairsData.iterrows():
+    calculated = simUtils.sentenceSimilarity(sentPair['sent1'], sentPair['sent2'], False)
+    # calculated = sentenceSimialrity(sentPair[0], sentPair[1], True)
+    senSimBestSense.append(calculated)
+    print(" ".join([str(e)+'\t' for e in sentPair.to_numpy()]), calculated)
+
+  senSimDisabiguate = []
+  print("Sentence Similarity with Disambiguation:")
+  for index, sentPair in sentencePairsData.iterrows():
+    calculated = simUtils.sentenceSimilarityWithDisambiguation(sentPair['sent1'], sentPair['sent2'], senseMethod='simple_lesk', simMethod='semantic_similarity_synsets', delta=1.0)
+    senSimDisabiguate.append(calculated)
+    print(" ".join([str(e)+'\t' for e in sentPair.to_numpy()]), calculated)
+
+  # simObj = SentenceSimilarity()
+  # print("Sentence Similarity Best Sense (Class Object):")
+  # for index, sentPair in sentencePairsData.iterrows():
+  #   calculated = simObj.sentenceSimilarity(sentPair['sent1'], sentPair['sent2'], method='best_sense')
+  #   # calculated = sentenceSimialrity(sentPair[0], sentPair[1], True)
+  #   print(" ".join([str(e)+'\t' for e in sentPair.to_numpy()]), calculated)
+
+  # print("Sentence Similarity PM_Disambiguation:")
+  # for index, sentPair in sentencePairsData.iterrows():
+  #   calculated = simObj.sentenceSimilarity(sentPair['sent1'], sentPair['sent2'], method='pm_disambiguation')
+  #   # calculated = sentenceSimialrity(sentPair[0], sentPair[1], True)
+  #   print(" ".join([str(e)+'\t' for e in sentPair.to_numpy()]), calculated)
+
+  def computePearson(name1, name2, data1, data2, type='Word'):
+    pcoef = stats.pearsonr(data1, data2).statistic
+    print(f'{type} Correlation ({name1} vs. {name2}):', pcoef)
+
+  if fromFile:
+    goldMetric = 'RG_similarity'
+    simMetrics = ['Lee2014', 'PV2018']
+    from scipy import stats
+    computePearson(name1=goldMetric, name2='semantic', data1=wordSimSemantic, data2=wordPairsData[goldMetric])
+    computePearson(name1=goldMetric, name2='Path', data1=wordSimPath, data2=wordPairsData[goldMetric])
+    for m in simMetrics:
+      computePearson(name1=goldMetric, name2=m, data1=wordPairsData[m], data2=wordPairsData[goldMetric])
+
+    goldMetric = 'MeanHumanSimilarity'
+    simMetrics = ['PV2018']
+    computePearson(name1=goldMetric, name2='semantic', data1=senSimBestSense, data2=sentencePairsData[goldMetric], type='Sentence Best Sense')
+    computePearson(name1=goldMetric, name2='semantic disambiguate', data1=senSimDisabiguate, data2=sentencePairsData[goldMetric], type='Sentence Disambiguation')
+    for m in simMetrics:
+      computePearson(name1=goldMetric, name2=m, data1=sentencePairsData[m], data2=sentencePairsData[goldMetric], type='Sentence')
+
diff --git a/src/nlp/similarity/simUtils.py b/src/nlp/similarity/simUtils.py
new file mode 100644
index 0000000..5b51ca7
--- /dev/null
+++ b/src/nlp/similarity/simUtils.py
@@ -0,0 +1,596 @@
+import sys
+import math
+import numpy as np
+# https://github.com/alvations/pywsd
+from pywsd.lesk import simple_lesk, original_lesk, cosine_lesk, adapted_lesk
+from pywsd import disambiguate
+from pywsd.similarity import max_similarity as maxsim
+
+import nltk
+from nltk import word_tokenize as tokenizer
+from nltk.corpus import brown
+from nltk.corpus import wordnet as wn
+from nltk.corpus import wordnet_ic
+
+"""
+  Methods proposed by: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1644735
+  Codes are modified from https://github.com/anishvarsha/Sentence-Similaritity-using-corpus-statistics
+"""
+
+def sentenceSimilarity(sentenceA, sentenceB, infoContentNorm=False, delta=0.85):
+  """
+    Compute sentence similarity using both semantic and word order similarity
+    The semantic similarity is based on maximum word similarity between one word and another sentence
+    @ In, sentenceA, str, first sentence used to compute sentence similarity
+    @ In, sentenceB, str, second sentence used to compute sentence similarity
+    @ In, infoContentNorm, bool, True if statistics corpus is used to weight similarity vectors
+    @ In, delta, float, [0,1], similarity contribution from semantic similarity, 1-delta is the similarity
+      contribution from word order similarity
+    @ Out, similarity, float, [0, 1], the computed similarity for given two sentences
+  """
+  similarity = delta * semanticSimilaritySentences(sentenceA, sentenceB, infoContentNorm) + (1.0-delta)* wordOrderSimilaritySentences(sentenceA, sentenceB)
+  return similarity
+
+
+def wordOrderSimilaritySentences(sentenceA, sentenceB):
+  """
+    Compute sentence similarity using word order similarity
+    @ In, sentenceA, str, first sentence used to compute sentence similarity
+    @ In, sentenceB, str, second sentence used to compute sentence similarity
+    @ Out, similarity, float, [0, 1], the computed word order similarity for given two sentences
+  """
+  wordsA = tokenizer(sentenceA)
+  wordsB = tokenizer(sentenceB)
+  wordSet = list(set(wordsA).union(set(wordsB)))
+  index = {word[1]: word[0] for word in enumerate(wordSet)}
+  r1 = constructWordOrderVector(wordsA, wordSet, index)
+  r2 = constructWordOrderVector(wordsB, wordSet, index)
+  srTemp = np.linalg.norm(r1-r2)/np.linalg.norm(r1+r2)
+  return 1-srTemp
+
+def constructWordOrderVector(words, jointWords, index):
+  """
+    Construct word order vector
+    @ In, words, set of words, a set of words for one sentence
+    @ In, jointWords, set of joint words, a set of joint words for both sentences
+    @ In, index, dict, word index in the joint set of words
+    @ Out, vector, numpy.array, the word order vector
+  """
+  vector = np.zeros(len(jointWords))
+  i = 0
+  wordSet = set(words)
+  for jointWord in jointWords:
+    if jointWord in wordSet:
+      vector[i] = index[jointWord]
+    else:
+      wordSimilar, similarity = identifyBestSimilarWordFromWordSet(jointWord, wordSet)
+      if similarity > 0.4:
+        vector[i] = index[wordSimilar]
+      else:
+        vector[i] = 0
+    i +=1
+  return vector
+
+def semanticSimilaritySentences(sentenceA, sentenceB, infoContentNorm):
+  """
+    Compute sentence similarity using semantic similarity
+    The semantic similarity is based on maximum word similarity between one word and another sentence
+    @ In, sentenceA, str, first sentence used to compute sentence similarity
+    @ In, sentenceB, str, second sentence used to compute sentence similarity
+    @ In, infoContentNorm, bool, True if statistics corpus is used to weight similarity vectors
+    @ Out, semSimilarity, float, [0, 1], the computed similarity for given two sentences
+  """
+  wordsA = tokenizer(sentenceA)
+  wordsB = tokenizer(sentenceB)
+  wordSet = set(wordsA).union(set(wordsB))
+  wordVectorA = constructSemanticVector(wordsA, wordSet, infoContentNorm)
+  wordVectorB = constructSemanticVector(wordsB, wordSet, infoContentNorm)
+
+  semSimilarity = np.dot(wordVectorA, wordVectorB)/(np.linalg.norm(wordVectorA)*np.linalg.norm(wordVectorB))
+  return semSimilarity
+
+
+def constructSemanticVector(words, jointWords, infoContentNorm):
+  """
+    Construct semantic vector
+    @ In, words, set of words, a set of words for one sentence
+    @ In, jointWords, set of joint words, a set of joint words for both sentences
+    @ In, infoContentNorm, bool, consider word statistics in Brown  Corpus if True
+    @ Out, vector, numpy.array, the semantic vector
+  """
+  wordSet = set(words)
+  vector = np.zeros(len(jointWords))
+  if infoContentNorm:
+    wordCount, brownDict = brownInfo()
+  i = 0
+  for jointWord in jointWords:
+    if jointWord in wordSet:
+      vector[i] = 1
+      if infoContentNorm:
+        vector[i] = vector[i]*math.pow(content(jointWord, wordCount, brownDict), 2)
+    else:
+      similarWord, similarity =  identifyBestSimilarWordFromWordSet(jointWord, wordSet)
+      if similarity >0.2:
+        vector[i] = similarity
+      else:
+        vector[i] = 0.0
+      if infoContentNorm:
+        vector[i] = vector[i]*content(jointWord, wordCount, brownDict)* content(similarWord, wordCount, brownDict)
+    i+=1
+  return vector
+
+def brownInfo():
+  """
+    Compute word dict and word numbers in NLTK brown corpus
+    @ In, None
+    @ Out, wordCount, int, the total number of words in brown
+    @ Out, brownDict, dict, the brown word dict, {word:count}
+  """
+  brownDict = {}
+  wordCount = 0
+  for sent in brown.sents():
+    for word in sent:
+      key = word.lower()
+      if key not in brownDict:
+        brownDict[key] = 0
+      brownDict[key]+=1
+      wordCount+=1
+  return wordCount, brownDict
+
+def content(wordData, wordCount=0, brownDict=None):
+  """
+    Employ statistics from Brown Corpus to compute the information content of given word in the corpus
+    ref: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1644735
+    information content I(w) = 1 - log(n+1)/log(N+1)
+    The significance of a word is weighted using its information content. The assumption here is
+    that words occur with a higher frequency (in corpus) contain less information than those
+    that occur with lower frequencies.
+    @ In, wordData, string, a given word
+    @ In, wordCount, int, the total number of words in brown corpus
+    @ In, brownDict, dict, the brown word dict, {word:count}
+    @ Out, content, float, [0, 1], the information content of a word in the corpus
+  """
+  if wordCount == 0:
+    wordCount, brownDict = brownInfo()
+  wordData = wordData.lower()
+  if wordData not in brownDict:
+    n = 0
+  else:
+    n = brownDict[wordData]
+  informationContent = math.log(n+1)/math.log(wordCount+1)
+  return 1.0-informationContent
+
+def identifyBestSimilarWordFromWordSet(wordA, wordSet):
+  """
+    Identify the best similar word in a word set for a given word
+    @ In, wordA, str, a given word that looking for the best similar word in a word set
+    @ In, wordSet, set/list, a pool of words
+    @ Out, word, str, the best similar word in the word set for given word
+    @ Out, similarity, float, [0, 1], similarity score between the best pair of words
+  """
+  similarity = 0.0
+  word = ""
+  for wordB in wordSet:
+    temp = semanticSimilarityWords(wordA, wordB)
+    if temp > similarity:
+      similarity = temp
+      word = wordB
+  return word, similarity
+
+def semanticSimilarityWords(wordA, wordB):
+  """
+    Compute the similarity between two words using semantic analysis
+    First identify the best similar synset pair using wordnet similarity, then compute the similarity
+    using both path length and depth information in wordnet
+    @ In, wordA, str, the first word
+    @ In, wordB, str, the second word
+    @ Out, similarity, float, [0, 1], the similarity score
+  """
+  if wordA.lower() == wordB.lower():
+    return 1.0
+  bestPair = identifyBestSimilarSynsetPair(wordA, wordB)
+  if bestPair[0] is None or bestPair[1] is None:
+    return 0.0
+  # disambiguation is False since only two words is provided and there is no additional information content
+  similarity = semanticSimilaritySynsets(bestPair[0], bestPair[1], disambiguation=False)
+  return similarity
+
+def semanticSimilaritySynsets(synsetA, synsetB, disambiguation=False):
+  """
+    Compute the similarity between two synset using semantic analysis
+    e.g., using both path length and depth information in wordnet
+    @ In, synsetA, wordnet.synset, the first synset
+    @ In, synsetB, wordnet.synset, the second synset
+    @ Out, similarity, float, [0, 1], the similarity score
+  """
+  shortDistance = pathLength(synsetA, synsetB, disambiguation=disambiguation)
+  maxHierarchy = scalingDepthEffect(synsetA, synsetB, disambiguation=disambiguation)
+  similarity = shortDistance*maxHierarchy
+  return similarity
+
+def identifyBestSimilarSynsetPair(wordA, wordB):
+  """
+    Identify the best synset pair for given two words using wordnet similarity analysis
+    @ In, wordA, str, the first word
+    @ In, wordB, str, the second word
+    @ Out, bestPair, tuple, (first synset, second synset), identified best synset pair using wordnet similarity
+  """
+  similarity = -1.0
+  synsetsWordA = wn.synsets(wordA)
+  synsetsWordB = wn.synsets(wordB)
+
+  if len(synsetsWordA) == 0 or len(synsetsWordB) == 0:
+    return None, None
+  else:
+    similarity = -1.0
+    bestPair = None, None
+    for synsetWordA in synsetsWordA:
+      for synsetWordB in synsetsWordB:
+        # TODO: may change to general similarity method
+        temp = wn.path_similarity(synsetWordA, synsetWordB)
+        if temp > similarity:
+          similarity = temp
+          bestPair = synsetWordA, synsetWordB
+    return bestPair
+
+def pathLength(synsetA, synsetB, alpha=0.2, disambiguation=False):
+  """
+    Path length calculation using nonlinear transfer function between two Wordnet Synsets
+    The two Synsets should be the best Synset Pair (e.g., disambiguation should be performed)
+    @ In, synsetA, wordnet.synset, synset for first word
+    @ In, synsetB, wordnet.synset, synset for second word
+    @ In, alpha, float, a constant in monotonically descreasing function, exp(-alpha*wordnetpathLength),
+      parameter used to scale the shortest path length. For wordnet, the optimal value is 0.2
+    @ In, disambiguation, bool, True if disambiguation have been performed for the given synsets
+    @ Out, shortDistance, float, [0, 1], the shortest distance between two synsets using exponential descreasing
+      function.
+  """
+  synsetA = wn.synset(synsetA.name())
+  synsetB = wn.synset(synsetB.name())
+  maxLength = sys.maxsize
+  if synsetA is None or synsetB is None:
+    return 0.0
+  # ? The original word is difference, but their synset are the same, we assume the path length is zero
+  if synsetA == synsetB:
+    maxLength = 0.0
+  else:
+    if not disambiguation:
+      lemmaSetA = set([str(word.name()) for word in synsetA.lemmas()])
+      lemmaSetB = set([str(word.name()) for word in synsetB.lemmas()])
+      #this line if the word is none
+      if len(lemmaSetA.intersection(lemmaSetB)) > 0:
+        maxLength = 1.0
+      else:
+        maxLength = synsetA.shortest_path_distance(synsetB)
+        if maxLength is None:
+          maxLength = 0.0
+    else:
+      # when disamigutation is performed, we should avoid to check lemmas
+      maxLength = synsetA.shortest_path_distance(synsetB)
+      if maxLength is None:
+        maxLength = 0.0
+  shortDistance = math.exp(-alpha*maxLength)
+  return shortDistance
+
+def scalingDepthEffect(synsetA, synsetB, beta=0.45, disambiguation=False):
+  """
+    Words at upper layers of hierarchical semantic nets have more general concepts and less semantic similarity
+    between words than words at lower layers. This method is used to scale the similarity behavior with repect
+    to depth h, e.g., [exp(beta*h)-exp(-beta*g)]/[exp(beta*h)+exp(-beta*g)]
+    The two Synsets should be the best Synset Pair (e.g., disambiguation should be performed)
+    @ In, synsetA, wordnet.synset, synset for first word
+    @ In, synsetB, wordnet.synset, synset for second word
+    @ In, beta, float, parameter used to scale the shortest depth, for wordnet, the optimal value is 0.45
+    @ In, disambiguation, bool, True if disambiguation have been performed for the given synsets
+    @ out, treeDist, float, [0, 1], similary score based on depth effect in wordnet
+  """
+  maxLength = sys.maxsize
+  smoothingFactor = beta
+  if synsetA is None or synsetB is None:
+    return 0.0
+
+  if synsetA == synsetB:
+    if disambiguation:
+      return 1.0
+    else:
+      # The following is from original code, I think it should be return 1.0 when synset are the same
+      # I think a new similarity calculations should be proposed
+      # values for different lengths and their similarity score:
+      # len simScore
+      # 0   0.0
+      # 1   0.4218990052500079
+      # 2   0.7162978701990244
+      # 3   0.874053287886007
+      # 4   0.9468060128462682
+      # 5   0.9780261147388136
+      # 6   0.9910074536781175
+      maxLength = max(word[1] for word in synsetA.hypernym_distances())
+  else:
+    hypernymWordA = {word[0]: word[1] for word in synsetA.hypernym_distances()}
+    hypernymWordB = {word[0]: word[1] for word in synsetB.hypernym_distances()}
+    commonValue = set(hypernymWordA.keys()).intersection(set(hypernymWordB.keys()))
+    if len(commonValue) <=0:
+      maxLength = 0
+    else:
+      distances = []
+      for common in commonValue:
+        commonWordADist = 0
+        commonWordBDist = 0
+        if common in hypernymWordA:
+          commonWordADist = hypernymWordA[common]
+        if common in hypernymWordB:
+          commonWordBDist = hypernymWordB[common]
+        maxDistance = max(commonWordADist, commonWordBDist)
+        distances.append(maxDistance)
+      maxLength = max(distances)
+  treeDist = (math.exp(smoothingFactor*maxLength)- math.exp(-smoothingFactor*maxLength))/(math.exp(smoothingFactor*maxLength) + math.exp(-smoothingFactor*maxLength))
+
+  return treeDist
+
+#################################################
+
+
+"""
+  Methods proposed by: https://arxiv.org/pdf/1802.05667.pdf
+  Codes are modified from https://github.com/nihitsaxena95/sentence-similarity-wordnet-sementic/blob/master/SentenceSimilarity.py
+"""
+
+def identifyNounAndVerbForComparison(sentence):
+  """
+    Taking out Noun and Verb for comparison word based
+    @ In, sentence, string, sentence string
+    @ Out, pos, list, list of dict {token/word:pos_tag}
+  """
+  tokens = nltk.word_tokenize(sentence)
+  pos = nltk.pos_tag(tokens)
+  pos = [p for p in pos if p[1].startswith('N') or p[1].startswith('V')]
+  return pos
+
+def sentenceSenseDisambiguation(sentence, method='simple_lesk'):
+  """
+    removing the disambiguity by getting the context
+    @ In, sentence, str, sentence string
+    @ In, method, str, the method for disambiguation, this method only support simple_lesk method
+    @ Out, sense, set, set of wordnet.Synset for the estimated best sense
+  """
+  pos = identifyNounAndVerbForComparison(sentence)
+  sense = []
+  for p in pos:
+    if method.lower() == 'simple_lesk':
+      sense.append(simple_lesk(sentence, p[0], pos=p[1][0].lower()))
+    else:
+      raise NotImplementedError(f"Mehtod {method} not implemented yet!")
+  return set(sense)
+
+###########################################################################
+
+"""
+  Extended methods
+"""
+def wordsSimilarity(wordA, wordB, method='semantic_similarity_synsets'):
+  """
+    General method for compute words similarity
+    @ In, wordA, str, the first word
+    @ In, wordB, str, the second word
+    @ In, method, str, the method used to compute word similarity
+    @ Out, similarity, float, [0, 1], the similarity score
+  """
+  method = method.lower()
+  wordnetSimMethod = ["path_similarity", "wup_similarity", "lch_similarity", "res_similarity", "jcn_similarity", "lin_similarity"]
+  sematicSimMethod = ['semantic_similarity_synsets']
+  if method not in sematicSimMethod and not method.endswith('_similarity'):
+    method = method + '_similarity'
+  if method not in sematicSimMethod + wordnetSimMethod:
+    raise ValueError(f'{method} is not valid, please use one of {wordnetSimMethod+sematicSimMethod}')
+  bestPair = identifyBestSimilarSynsetPair(wordA, wordB)
+  if bestPair[0] is None or bestPair[1] is None:
+    return 0.0
+  # when campare words only, we assume there is no disambiguation required.
+
+  similarity = synsetsSimilarity(bestPair[0], bestPair[1], method=method, disambiguation=False)
+  return similarity
+
+# TODO: Utilize Spacy wordvector similarity to improve the similarity score when the POS for given synset are not the same
+# i.e., synsetA.name().split(".")[1] != synsetB.name().split(".")[1], the similarity will be set to 0.0
+# Similarity calculated by word1.similarity(word2)
+# calibration calibrate 0.715878
+# failure fail 0.6802347
+# replacement replace 0.73397416
+# leak leakage 0.652573
+
+# Similarity calculated by wordnet:
+# calibration calibrate 0.09090909090909091
+# failure fail 0.125
+# replacement replace 0.1111111111111111
+# leak leakage 1.0
+
+def synsetsSimilarity(synsetA, synsetB, method='semantic_similarity_synsets', disambiguation=True):
+  """
+    Compute synsets similarity
+    @ In, synsetA, wordnet.synset, the first synset
+    @ In, synsetB, wordnet.synset, the second synset
+    @ In, method, str, the method used to compute synset similarity
+      one of ['semantic_similarity_synsets', 'path', 'wup', 'lch', 'res', 'jcn', 'lin']
+    @ In, disambiguation, bool, True if disambiguation has been already performed
+    @ Out, similarity, float, [0, 1], the similarity score
+  """
+  method = method.lower()
+  if method != 'semantic_similarity_synsets' and not method.endswith('_similarity'):
+    method = '_'.join([method, 'similarity'])
+  wordnetSimMethod = ["path_similarity", "wup_similarity", "lch_similarity", "res_similarity", "jcn_similarity", "lin_similarity"]
+  sematicSimMethod = ['semantic_similarity_synsets']
+  synsetA = wn.synset(synsetA.name())
+  synsetB = wn.synset(synsetB.name())
+  if method in wordnetSimMethod:
+    if method in ["path_similarity", "wup_similarity"]:
+      similarity = getattr(wn, method)(synsetA, synsetB)
+    elif method == "lch_similarity":
+      if synsetA.name().split(".")[1] == synsetB.name().split(".")[1]:
+        similarity = getattr(wn, method)(synsetA, synsetB)
+      else:
+        similarity = 0.0
+    else:
+      brownIc = wordnet_ic.ic('ic-brown.dat')
+      if synsetA.name().split(".")[1] == synsetB.name().split(".")[1]:
+        try:
+          similarity = getattr(wn, method)(synsetA, synsetB, brownIc)
+        except:
+          similarity = 0.0
+      else:
+        similarity = 0.0
+  elif method in sematicSimMethod:
+    similarity = semanticSimilaritySynsets(synsetA, synsetB, disambiguation=disambiguation)
+  else:
+    raise ValueError(f'{method} is not valid, please use one of {wordnetSimMethod+sematicSimMethod}')
+
+  return similarity
+
+
+def wordSenseDisambiguation(word, sentence, senseMethod='simple_lesk', simMethod='path'):
+  """
+    removing the disambiguity by getting the context
+    @ In, word, str/list/set, given word or set of words
+    @ In, sentence, str, sentence that will be used to disambiguate the given word
+    @ In, senseMethod, str, method for disambiguation, one of ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']
+    @ In, simMethod, str, method for similarity analysis when 'max_similarity' is used,
+      one of ['path', 'wup', 'lch', 'res', 'jcn', 'lin']
+    @ Out, sense, str/list/set, the type for given word, identified best sense for given word with disambiguation performed using given sentence
+  """
+  method = senseMethod.lower()
+  simMethod = simMethod.lower()
+  validMethod = ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']
+  validSimMethod = ['path', 'wup', 'lch', 'res', 'jcn', 'lin']
+  if method not in validMethod:
+    raise ValueError(f'{method} is not valid option, please try to use one of {validMethod}')
+  if simMethod not in validSimMethod:
+    raise ValueError(f'{simMethod} is not valid option, please try to use one of {validSimMethod}')
+  if isinstance(word, str):
+    tokens = nltk.word_tokenize(word)
+  elif isinstance(word, list) or isinstance(word, set):
+    tokens = [nltk.word_tokenize(w)[0] for w in word]
+  else:
+    raise ValueError(f'{word} format is not valid, please input string, set/list of string')
+  pos = nltk.pos_tag(tokens)
+  sense = []
+  for p in pos:
+    if method == 'simple_lesk':
+      sense.append(simple_lesk(sentence, p[0], pos=p[1][0].lower()))
+    elif method == 'original_lesk':
+      sense.append(original_lesk(sentence, p[0]))
+    elif method == 'adapted_lesk':
+      sense.append(adapted_lesk(sentence, p[0], pos=p[1][0].lower()))
+    elif method == 'cosine_lesk':
+      sense.append(cosine_lesk(sentence, p[0], pos=p[1][0].lower()))
+    elif method == 'max_similarity':
+      sense.append(maxsim(sentence, p[0], pos=p[1][0].lower(), option=simMethod))
+    else:
+      raise NotImplementedError(f"Mehtod {method} not implemented yet!")
+  if isinstance(word, str):
+    return sense[0]
+  elif isinstance(word, list):
+    return sense
+  else:
+    return set(sense)
+
+
+# use disambiguate function to disambiguate sentences
+def sentenceSenseDisambiguationPyWSD(sentence, senseMethod='simple_lesk', simMethod='path'):
+  """
+    Wrap for sentence sense disambiguation method from pywsd
+    https://github.com/alvations/pywsd
+    @ In, sentence, str, given sentence
+    @ In, senseMethod, str, method for disambiguation, one of ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']
+    @ In, simMethod, str, method for similarity analysis when 'max_similarity' is used,
+      one of ['path', 'wup', 'lch', 'res', 'jcn', 'lin']
+    @ Out, wordList, list, list of words from sentence that has an identified synset from wordnet
+    @ Out, synsetList, list, list of corresponding synset for wordList
+  """
+  method = senseMethod.lower()
+  simMethod = simMethod.lower()
+  validMethod = ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']
+  validSimMethod = ['path', 'wup', 'lch', 'res', 'jcn', 'lin']
+  if method not in validMethod:
+    raise ValueError(f'{method} is not valid option, please try to use one of {validMethod}')
+  if simMethod not in validSimMethod:
+    raise ValueError(f'{simMethod} is not valid option, please try to use one of {validSimMethod}')
+  if method == 'simple_lesk':
+    sentSense = disambiguate(sentence, simple_lesk, prefersNone=True, keepLemmas=True)
+  elif method == 'original_lesk':
+    sentSense = disambiguate(sentence, original_lesk, prefersNone=True, keepLemmas=True)
+  elif method == 'adapted_lesk':
+    sentSense = disambiguate(sentence, adapted_lesk, prefersNone=True, keepLemmas=True)
+  elif method == 'cosine_lesk':
+    sentSense = disambiguate(sentence, cosine_lesk, prefersNone=True,  keepLemmas=True)
+  elif method == 'max_similarity':
+    sentSense = disambiguate(sentence, maxsim, similarity_option=simMethod, prefersNone=True,  keepLemmas=True)
+  # sentSense: a list of tuples, [(word, lemma, wn.synset/None)]
+  wordList = list([syn[0] for syn in sentSense if syn[-1] is not None])
+  synsetList = list([syn[-1] for syn in sentSense if syn[-1] is not None])
+  return wordList, synsetList
+
+# Sentence similarity after disambiguation
+
+def sentenceSimilarityWithDisambiguation(sentenceA, sentenceB, senseMethod='simple_lesk', simMethod='semantic_similarity_synsets', disambiguationSimMethod='path', delta=0.85):
+  """
+    Compute semantic similarity for given two sentences that disambiguation will be performed
+    @ In, sentenceA, str, first sentence
+    @ In, sentenceB, str, second sentence
+    @ In, senseMethod, str, method for disambiguation, one of ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']
+    @ In, simMethod, str, method for similarity analysis in the construction of semantic vectors
+      one of ['semantic_similarity_synsets', 'path', 'wup', 'lch', 'res', 'jcn', 'lin']
+    @ In, disambiguationSimMethod, str, method for similarity analysis when 'max_similarity' is used,
+      one of ['path', 'wup', 'lch', 'res', 'jcn', 'lin']
+    @ In, delta, float, [0,1], similarity contribution from semantic similarity, 1-delta is the similarity
+      contribution from word order similarity
+    @ Out, similarity, float, [0, 1], the computed similarity for given two sentences
+  """
+  simMethod = simMethod.lower()
+  disambiguationSimMethod = disambiguationSimMethod.lower()
+  if disambiguationSimMethod not in ['path', 'wup', 'lch', 'res', 'jcn', 'lin']:
+    raise ValueError(f'Option for "disambiguationSimMethod={disambiguationSimMethod}" is not valid, please try one of "path, wup, lch, res, jcn, lin" ')
+  _, synsetsA = sentenceSenseDisambiguationPyWSD(sentenceA, senseMethod=senseMethod, simMethod=disambiguationSimMethod)
+  _, synsetsB = sentenceSenseDisambiguationPyWSD(sentenceB, senseMethod=senseMethod, simMethod=disambiguationSimMethod)
+  similarity = delta * semanticSimilarityUsingDisambiguatedSynsets(synsetsA, synsetsB, simMethod=simMethod) + (1.0-delta)* wordOrderSimilaritySentences(sentenceA, sentenceB)
+  return similarity
+
+def semanticSimilarityUsingDisambiguatedSynsets(synsetsA, synsetsB, simMethod='semantic_similarity_synsets'):
+  """
+    Compute semantic similarity for given synsets while disambiguation has been already performed for given synsets
+    @ In, synsetsA, set/list, list of synsets
+    @ In, synsetsB, set/list, list of synsets
+    @ In, simMethod, str, method for similarity analysis in the construction of semantic vectors
+      one of ['semantic_similarity_synsets', 'path', 'wup', 'lch', 'res', 'jcn', 'lin']
+    @ Out, semSimilarity, float, [0, 1], the similarity score
+  """
+  jointWordSynsets = set(synsetsA).union(set(synsetsB))
+  wordVectorA = constructSemanticVectorUsingDisambiguatedSynsets(synsetsA, jointWordSynsets, simMethod=simMethod)
+  wordVectorB = constructSemanticVectorUsingDisambiguatedSynsets(synsetsB, jointWordSynsets, simMethod=simMethod)
+  semSimilarity = np.dot(wordVectorA, wordVectorB)/(np.linalg.norm(wordVectorA)*np.linalg.norm(wordVectorB))
+  return semSimilarity
+
+def constructSemanticVectorUsingDisambiguatedSynsets(wordSynsets, jointWordSynsets, simMethod='semantic_similarity_synsets'):
+  """
+    Construct semantic vector while disambiguation has been already performed
+    @ In, wordSynsets, set/list, set of words synsets
+    @ In, jointWords, set, set of joint words synsets
+    @ In, simMethod, str, method for similarity analysis in the construction of semantic vectors
+      one of ['semantic_similarity_synsets', 'path', 'wup', 'lch', 'res', 'jcn', 'lin']
+    @ Out, vector, numpy.array, semantic vector with disambiguation
+  """
+  wordSynsets = set(wordSynsets)
+  vector = np.zeros(len(jointWordSynsets))
+  for i, jointSynset in enumerate(jointWordSynsets):
+    simVector = []
+    if jointSynset in wordSynsets:
+      vector[i] = 1
+    else:
+      for synsetB in wordSynsets:
+        similarity = synsetsSimilarity(jointSynset, synsetB, method=simMethod, disambiguation=True)
+        simVector.append(similarity)
+      maxSim = max(simVector)
+      # if similarity < 0.2, treat it as noise and reset it to 0.0
+      if maxSim >= 0.2:
+        vector[i] = maxSim
+      else:
+        vector[i] = 0.0
+  return vector


Test patch:
diff --git a/src/nlp/similarity/test_pm_method.py b/src/nlp/similarity/test_pm_method.py
new file mode 100644
index 0000000..3841d6a
--- /dev/null
+++ b/src/nlp/similarity/test_pm_method.py
@@ -0,0 +1,10 @@
+from SentenceSimilarity import SentenceSimilarity
+
+
+
+sents = ['A gem is a jewel or stone that is used in jewellery.', 'A jewel is a precious stone used to decorate valuable things that you wear, such as rings or necklaces.']
+
+simObj = SentenceSimilarity()
+print("Sentence Similarity PM_Disambiguation:")
+calculated = simObj.sentenceSimilarity(sents[0], sents[1], method='pm_disambiguation')
+print(" ".join([str(e)+'\t' for e in sents]), calculated)
diff --git a/src/nlp/similarity/test_simUtils.py b/src/nlp/similarity/test_simUtils.py
new file mode 100644
index 0000000..ceaace2
--- /dev/null
+++ b/src/nlp/similarity/test_simUtils.py
@@ -0,0 +1,60 @@
+import simUtils
+
+
+bankSents = ['I went to the bank to deposit my money',
+'The river bank was full of dead fishes']
+
+plantSents = ['The workers at the industrial plant were overworked',
+'The plant was no longer bearing flowers']
+
+print("================ Testing sentenceSenseDisambiguationPyWSD ================\n")
+for method in ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']:
+  simMethod = 'path'
+  for sent in bankSents:
+    wordList, synsetList = simUtils.sentenceSenseDisambiguationPyWSD(sent, senseMethod=method, simMethod=simMethod)
+    print(sent)
+    print('==>', method, wordList, synsetList)
+
+
+print("================ Testing wordSenseDisambiguation ================\n")
+for method in ['simple_lesk', 'original_lesk', 'cosine_lesk', 'adapted_lesk', 'max_similarity']:
+  simMethod = 'path'
+  word = 'plant'
+  for sent in plantSents:
+    synsetList = simUtils.wordSenseDisambiguation(word, sent, senseMethod=method, simMethod=simMethod)
+    print(sent)
+    print('==>', method, synsetList)
+
+
+print("================ Check two words similarity in two sentences ================\n")
+word = 'plant'
+method = 'simple_lesk'
+simMethod = 'path'
+sent = plantSents
+synsetA = simUtils.wordSenseDisambiguation(word, sent[0], senseMethod=method, simMethod=simMethod)
+synsetB = simUtils.wordSenseDisambiguation(word, sent[1], senseMethod=method, simMethod=simMethod)
+for m in ["path_similarity", "wup_similarity", "lch_similarity", "res_similarity", "jcn_similarity", "lin_similarity"] + ['semantic_similarity_synsets']:
+  similarity = simUtils.synsetsSimilarity(synsetA, synsetB, m)
+  print(word, 'synsetA:', synsetA, 'synsetB:', synsetB, 'method:', m, 'similarity:', similarity)
+
+
+word = 'bank'
+sent = bankSents
+synsetA = simUtils.wordSenseDisambiguation(word, sent[0], senseMethod=method, simMethod=simMethod)
+synsetB = simUtils.wordSenseDisambiguation(word, sent[1], senseMethod=method, simMethod=simMethod)
+for m in ["path_similarity", "wup_similarity", "lch_similarity", "res_similarity", "jcn_similarity", "lin_similarity"] + ['semantic_similarity_synsets']:
+  similarity = simUtils.synsetsSimilarity(synsetA, synsetB, m)
+  print(word, 'synsetA:', synsetA, 'synsetB:', synsetB, 'method:', m, 'similarity:', similarity)
+
+
+print("================ Testing sentenceSimilarity ================\n")
+sents = [bankSents, plantSents]
+for sent in sents:
+  similarity = simUtils.sentenceSimilarity(sent[0], sent[1], infoContentNorm=False, delta=0.85)
+  print(sent[0], 'vs', sent[1], 'similarity', similarity)
+
+print("================ Testing sentenceSimilarityWithDisambiguation ================\n")
+sents = [bankSents, plantSents]
+for sent in sents:
+  similarity = simUtils.sentenceSimilarityWithDisambiguation(sent[0], sent[1], senseMethod='simple_lesk', simMethod='semantic_similarity_synsets', delta=0.85)
+  print(sent[0], 'vs', sent[1], 'similarity', similarity)