Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
encyclopedia-gui
Commits
a595f6a5
Commit
a595f6a5
authored
Feb 18, 2021
by
Lauri Himanen
Browse files
Made a separate file and class for the optimade translation functionality.
parent
e35871eb
Pipeline
#93732
skipped with stage
Changes
5
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
client/bundle.js
View file @
a595f6a5
This source diff could not be displayed because it is too large. You can
view the blob
instead.
client/src/search-mod/Formula.js
0 → 100644
View file @
a595f6a5
let
util
=
require
(
'
../common/util.js
'
);
class
Formula
{
constructor
(
formula
)
{
this
.
formula
=
formula
;
this
.
formulaMap
=
this
.
_parseFormula
(
formula
);
console
.
log
(
'
this.formulaMap:
'
,
this
.
formulaMap
);
}
_parseFormula
(
formula
){
let
index
=
0
;
let
map
=
new
Map
();
let
key
;
while
(
index
<
formula
.
length
){
let
el2
=
formula
.
substring
(
index
,
index
+
2
);
let
el1
=
formula
.
substring
(
index
,
index
+
1
);
if
(
util
.
ELEMENTS
.
indexOf
(
el2
)
>=
0
){
map
.
set
(
el2
,
1
);
// 1 default value
index
+=
2
;
key
=
el2
;
//console.log('eleemnt 2chars', key);
}
else
if
(
util
.
ELEMENTS
.
indexOf
(
el1
)
>=
0
){
map
.
set
(
el1
,
1
);
// 1 default value
index
++
;
key
=
el1
;
//console.log('eleemnt 1chars', key);
}
else
{
// It's a number
let
num
=
parseInt
(
el2
);
if
(
num
>=
10
)
index
+=
2
;
// 2 figures number
else
index
++
;
// 1 figure number
//console.log('number ', num, key);
map
.
set
(
key
,
num
);
}
// console.log('FINAL LOOP', map, index);
}
return
map
;
}
getOptimadeSubquery
(
allowOtherElements
)
{
const
fragments
=
[]
this
.
formulaMap
.
forEach
(
(
number
,
element
)
=>
{
const
fragment
=
'
"
'
+
element
+
(
number
===
1
?
''
:
+
number
)
+
'
"
'
;
fragments
.
push
(
fragment
)
})
return
'
formula HAS
'
+
(
allowOtherElements
?
'
ALL
'
:
'
ONLY
'
)
+
fragments
.
join
(
'
,
'
)
}
getFragments
()
{
const
fragments
=
[]
this
.
formulaMap
.
forEach
(
(
number
,
element
)
=>
{
const
fragment
=
'
"
'
+
element
+
(
number
===
1
?
''
:
+
number
)
+
'
"
'
;
fragments
.
push
(
fragment
);
})
return
fragments
;
}
/*
getReducedFormula(getTokens = true){
let counter = 0;
while ( !checkIfReduced(this.formulaMap) ){ // console.log('Reducing', this.formulaMap);
let div = 1;
if (isDivisibleBy(this.formulaMap, 2)) div = 2;
else if (isDivisibleBy(this.formulaMap, 3)) div = 3;
else if (isDivisibleBy(this.formulaMap, 5)) div = 5;
else if (isDivisibleBy(this.formulaMap, 7)) div = 7;
else if (isDivisibleBy(this.formulaMap, 11)) div = 11;
this.formulaMap.forEach( (value, key) => {
this.formulaMap.set(key, (value/div));
});
//console.log('Reducing DIV', this.formulaMap);
counter++;
if (counter > 5) break;
}
function checkIfReduced(formulaMap){
let min = 100;
formulaMap.forEach( (value, key) => {
if (value < min) min = value;
});
return min === 1;
}
function isDivisibleBy(formulaMap, n){
let div = true;
formulaMap.forEach( (value, key) => {
if (value % n !== 0) div = false;
});
return div;
}
let tokens = [];
let canonicalFormula = '';
if (getTokens){
this.formulaMap.forEach( (value, key) => tokens.push(key+value) );
}else{
let sortedElements = this._sortElements( Array.from( this.formulaMap.keys() ) );
sortedElements.forEach( element => {
canonicalFormula += element+this.formulaMap.get(element);
//canonicalFormula += element+(map.get(element) === 1 ? '' : map.get(element));
});
}
console.log('_reduceFormula RETURN: ', this.formulaMap, tokens, canonicalFormula);
return (getTokens ? tokens : canonicalFormula);
}
*/
}
// EXPORTS
module
.
exports
=
Formula
;
client/src/search-mod/NewSearchMod.js
View file @
a595f6a5
...
...
@@ -35,7 +35,7 @@ let FilterPanel = require('./FilterPanel.view.js');
let
SwitchComponent
=
require
(
'
../common/SwitchComponent.js
'
);
const
REACTIVE_SEARCH
=
tru
e
;
const
REACTIVE_SEARCH
=
fals
e
;
function
replaceDashes
(
s
){
return
s
.
split
(
'
-
'
).
join
(
'
_
'
);
...
...
client/src/search-mod/OptimadeTranslator.js
0 → 100644
View file @
a595f6a5
let
Formula
=
require
(
'
./Formula.js
'
);
/**
* This class is used to translate the GUI query language into a corresponding
* Optimade query.
*/
class
OptimadeTranslator
{
constructor
()
{
}
/**
* Translates the given GUI query into the corresponding optimade query
* string.
*/
translate
(
queryIn
,
typesIn
,
allowOtherElements
)
{
// Make a copy so that this function becomes pure.
let
query
=
[...
queryIn
];
let
types
=
[...
typesIn
];
// Add escaped quotes to all elements
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
let
q
=
query
[
i
];
let
t
=
types
[
i
];
if
(
t
===
"
E
"
)
{
query
[
i
]
=
`\"
${
q
}
\"`
}
}
// Add escaped quotes to formulas
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
let
q
=
query
[
i
];
let
t
=
types
[
i
];
if
(
t
===
"
F
"
)
{
const
formula
=
new
Formula
(
q
)
const
fragments
=
[]
formula
.
formulaMap
.
forEach
(
(
number
,
element
)
=>
{
const
fragment
=
'
"
'
+
element
+
(
number
===
1
?
''
:
+
number
)
+
'
"
'
;
fragments
.
push
(
fragment
)
})
query
[
i
]
=
fragments
.
join
(
'
,
'
)
}
}
// Construct the reduced expression
[
query
,
types
]
=
this
.
simplify
(
query
,
types
);
// Convert into optimade syntax
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
let
q
=
query
[
i
];
let
t
=
types
[
i
];
if
(
t
===
"
E
"
)
{
if
(
allowOtherElements
)
{
q
=
"
elements HAS ALL
"
+
q
}
else
{
q
=
"
elements HAS ONLY
"
+
q
}
}
else
if
(
t
===
"
F
"
)
{
if
(
allowOtherElements
)
{
q
=
"
formula HAS ALL
"
+
q
}
else
{
q
=
"
formula HAS ONLY
"
+
q
}
}
query
[
i
]
=
q
;
}
let
res
=
query
.
join
(
"
"
)
console
.
log
(
"
FINAL OPTIMADE QUERY:
"
)
console
.
log
(
res
)
return
res
}
/**
* For combining elements/formulas connected by AND into a single list.
*/
combineANDIn
(
query
,
types
)
{
const
newQuery
=
[]
const
newTypes
=
[]
for
(
let
i
=
0
;
i
<
query
.
length
;)
{
const
q0
=
query
[
i
-
1
];
const
t0
=
types
[
i
-
1
];
const
q1
=
query
[
i
];
const
t1
=
types
[
i
];
const
q2
=
query
[
i
+
1
];
const
t2
=
types
[
i
+
1
];
const
q3
=
query
[
i
+
2
];
const
t3
=
types
[
i
+
2
];
if
(((
t1
===
"
E
"
&&
t3
===
"
E
"
)
||
(
t1
===
"
F
"
&&
t3
===
"
F
"
))
&&
q2
===
"
AND
"
&&
q0
!==
"
NOT
"
)
{
let
active
=
true
let
type
=
t3
let
joined
=
[
q1
,
q3
]
i
=
i
+
3
while
(
active
)
{
const
q4
=
query
[
i
];
const
t4
=
types
[
i
];
const
q5
=
query
[
i
+
1
];
const
t5
=
types
[
i
+
1
];
if
(
q4
===
"
AND
"
&&
t5
===
type
)
{
joined
.
push
(
q5
)
i
+=
2
}
else
{
active
=
false
newQuery
.
push
(
joined
.
join
(
"
,
"
))
newTypes
.
push
(
type
)
}
}
}
else
{
newQuery
.
push
(
q1
)
newTypes
.
push
(
t1
)
i
+=
1
}
}
return
[
newQuery
,
newTypes
]
}
/**
* For finding parenthesis pairs
*/
findPairs
(
query
,
types
)
{
const
pairs
=
new
Map
();
// Find list of indices where parenthesis should be removed
let
removed
=
[]
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
const
q1
=
query
[
i
];
const
t1
=
types
[
i
];
if
(
q1
===
"
(
"
)
{
let
index
=
0
;
for
(
let
j
=
i
+
1
;
j
<
query
.
length
;
++
j
)
{
const
q2
=
query
[
j
];
const
t2
=
types
[
j
];
if
(
q2
===
"
(
"
)
{
index
+=
1
;
}
if
(
q2
===
"
)
"
)
{
if
(
index
!=
0
)
{
index
-=
1
;
}
else
{
pairs
.
set
(
i
,
j
)
pairs
.
set
(
j
,
i
)
break
}
}
}
}
}
return
pairs
;
}
/**
* For removing unnecessary parenthesis
*/
removeParenthesis
(
query
,
types
)
{
// Find list of indices where parenthesis should be removed
const
pairs
=
this
.
findPairs
(
query
,
types
)
let
removed
=
[]
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
const
q1
=
query
[
i
];
const
t1
=
types
[
i
];
if
(
q1
===
"
(
"
)
{
let
p
=
pairs
.
get
(
i
)
// Remove outer parentheses
if
(
i
===
0
&&
p
==
query
.
length
-
1
)
{
removed
.
push
(
p
)
removed
.
push
(
i
)
// Remove parentheses that do not contain multiple operations
}
else
{
const
contents
=
query
.
slice
(
i
+
1
,
p
)
const
tp
=
types
.
slice
(
i
+
1
,
p
)
let
noop
=
true
for
(
let
t
of
tp
)
{
if
(
t
===
"
S
"
)
{
noop
=
false
break
}
}
if
(
noop
)
{
removed
.
push
(
p
)
removed
.
push
(
i
)
}
}
}
}
// Find repeated quotes and reduce them to single one
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
const
q1
=
query
[
i
];
const
t1
=
types
[
i
];
const
q2
=
query
[
i
+
1
];
const
t2
=
types
[
i
+
1
];
if
(
q1
===
"
(
"
&&
q1
===
"
(
"
)
{
let
p1
=
pairs
.
get
(
i
)
let
p2
=
pairs
.
get
(
i
+
1
)
if
(
p2
==
p1
-
1
)
{
removed
.
push
(
i
)
removed
.
push
(
p1
)
}
}
}
// Remove found parentheses
removed
=
removed
.
sort
((
a
,
b
)
=>
b
-
a
);
for
(
let
j
=
0
;
j
<
removed
.
length
;
++
j
)
{
query
.
splice
(
removed
[
j
],
1
);
types
.
splice
(
removed
[
j
],
1
);
}
return
[
query
,
types
]
}
/**
* Combines AND statements where either one or both of the statements has a
* parenthesis. These statements are refactored using distributive laws of
* set theory:
* A AND (B OR C) == (A AND B) OR (A AND C)
* in order to see if there are lists of elements that should be joined.
*/
combineANDOut
(
query
,
types
)
{
let
newQuery
=
query
let
newTypes
=
types
const
pairs
=
this
.
findPairs
(
query
,
types
)
let
combined
=
false
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
// Look for AND surrounded by one or more parenthesis
const
q1
=
query
[
i
];
const
t1
=
types
[
i
];
const
q2
=
query
[
i
+
1
];
const
t2
=
types
[
i
+
1
];
const
q3
=
query
[
i
+
2
];
const
t3
=
types
[
i
+
2
];
if
(
q2
===
"
AND
"
&&
(
q1
===
"
)
"
||
q3
===
"
(
"
))
{
combined
=
true
// Get left hand and right hand side expressions
let
lhsQ
let
lhsT
let
rhsQ
let
rhsT
let
rhsEnd
let
lhsStart
if
(
q1
===
"
)
"
)
{
lhsStart
=
pairs
.
get
(
i
)
lhsQ
=
query
.
slice
(
lhsStart
,
i
+
1
)
lhsT
=
types
.
slice
(
lhsStart
,
i
+
1
)
}
else
{
if
(
query
[
i
-
1
]
===
"
NOT
"
)
{
lhsStart
=
i
-
1
}
else
{
lhsStart
=
i
}
lhsQ
=
query
.
slice
(
lhsStart
,
i
+
1
)
lhsT
=
types
.
slice
(
lhsStart
,
i
+
1
)
lhsQ
.
unshift
(
"
(
"
)
lhsT
.
unshift
(
"
P
"
)
lhsQ
.
push
(
"
)
"
)
lhsT
.
push
(
"
P
"
)
}
if
(
q3
===
"
(
"
)
{
rhsEnd
=
pairs
.
get
(
i
+
2
)
+
1
rhsQ
=
query
.
slice
(
i
+
2
,
rhsEnd
)
rhsT
=
types
.
slice
(
i
+
2
,
rhsEnd
)
}
else
{
if
(
query
[
i
+
2
]
===
"
NOT
"
)
{
rhsEnd
=
i
+
4
}
else
{
rhsEnd
=
i
+
3
}
rhsQ
=
query
.
slice
(
i
+
2
,
rhsEnd
)
rhsT
=
types
.
slice
(
i
+
2
,
rhsEnd
)
rhsQ
.
unshift
(
"
(
"
)
rhsT
.
unshift
(
"
P
"
)
rhsQ
.
push
(
"
)
"
)
rhsT
.
push
(
"
P
"
)
}
// Split the rhs into parts separated by OR
const
rhsParts
=
[]
let
prevPos
=
1
for
(
let
j
=
1
;
j
<
rhsQ
.
length
-
1
;
++
j
)
{
const
sq1
=
rhsQ
[
j
];
if
(
sq1
===
"
OR
"
)
{
rhsParts
.
push
([
rhsQ
.
slice
(
prevPos
,
j
),
rhsT
.
slice
(
prevPos
,
j
)])
prevPos
=
j
+
1
}
else
if
(
j
===
rhsQ
.
length
-
2
)
{
rhsParts
.
push
([
rhsQ
.
slice
(
prevPos
,
j
+
1
),
rhsT
.
slice
(
prevPos
,
j
+
1
)])
}
}
// Combine lhs with the different parts of rhs
let
comboQ
=
[]
let
comboT
=
[]
for
(
let
j
=
0
;
j
<
rhsParts
.
length
;
++
j
)
{
let
icomboQ
=
[]
let
icomboT
=
[]
icomboQ
=
icomboQ
.
concat
(
rhsParts
[
j
][
0
])
icomboT
=
icomboT
.
concat
(
rhsParts
[
j
][
1
])
icomboQ
.
push
(
"
AND
"
)
icomboT
.
push
(
"
S
"
)
icomboQ
=
icomboQ
.
concat
(
lhsQ
)
icomboT
=
icomboT
.
concat
(
lhsT
)
if
(
j
===
0
)
{
if
(
rhsParts
.
length
>
1
)
{
comboQ
.
push
(
"
(
"
)
comboT
.
push
(
"
P
"
)
}
comboQ
=
comboQ
.
concat
(
icomboQ
)
comboT
=
comboT
.
concat
(
icomboT
)
if
(
rhsParts
.
length
>
1
)
{
comboQ
.
push
(
"
)
"
)
comboT
.
push
(
"
P
"
)
}
}
else
{
comboQ
.
push
(
"
OR
"
)
comboT
.
push
(
"
S
"
)
comboQ
.
push
(
"
(
"
)
comboT
.
push
(
"
P
"
)
comboQ
=
comboQ
.
concat
(
icomboQ
)
comboT
=
comboT
.
concat
(
icomboT
)
comboQ
.
push
(
"
)
"
)
comboT
.
push
(
"
P
"
)
}
}
comboQ
.
unshift
(
"
(
"
)
comboT
.
unshift
(
"
P
"
)
comboQ
.
push
(
"
)
"
)
comboT
.
push
(
"
P
"
)
newQuery
=
query
.
slice
(
0
,
lhsStart
).
concat
(
comboQ
).
concat
(
query
.
slice
(
rhsEnd
,
query
.
length
))
newTypes
=
types
.
slice
(
0
,
lhsStart
).
concat
(
comboT
).
concat
(
types
.
slice
(
rhsEnd
,
query
.
length
))
break
}
}
return
[
newQuery
,
newTypes
,
combined
]
}
/**
* Recursive function for simplifying the query into a form that can be
* correctly interpreted by Optimade.
*/
simplify
(
query
,
types
)
{
// Remove unnecessary parenthesis, combine all elements connected with
// AND, and remove unnecessary parenthesis again.
[
query
,
types
]
=
this
.
removeParenthesis
(
query
,
types
);
[
query
,
types
]
=
this
.
combineANDIn
(
query
,
types
);
[
query
,
types
]
=
this
.
removeParenthesis
(
query
,
types
);
// Combine parenthesis statements
let
combined
[
query
,
types
,
combined
]
=
this
.
combineANDOut
(
query
,
types
);
// If a combination was done, run recursively until no combination is
// done
if
(
combined
)
{
[
query
,
types
]
=
this
.
simplify
(
query
,
types
)
// After the last step perform on final cleanup
}
else
{
[
query
,
types
]
=
this
.
removeParenthesis
(
query
,
types
);
[
query
,
types
]
=
this
.
combineANDIn
(
query
,
types
);
[
query
,
types
]
=
this
.
removeParenthesis
(
query
,
types
);
}
return
[
query
,
types
]
}
}
// EXPORTS
module
.
exports
=
OptimadeTranslator
;
client/src/search-mod/SearchBox.view.js
View file @
a595f6a5
...
...
@@ -24,6 +24,8 @@
"
use strict
"
;
let
util
=
require
(
'
../common/util.js
'
);
let
OptimadeTranslator
=
require
(
'
./OptimadeTranslator.js
'
);
let
Formula
=
require
(
'
./Formula.js
'
);
//
//
//// local utility functions
...
...
@@ -82,344 +84,19 @@ class SearchBox{
}
getOptimadeQuery2
(
allowOtherElements
)
{
let
query
=
[...
this
.
searchQuery
];
let
types
=
[...
this
.
queryTypes
];
// For combining elements/formulas connected by AND into a single list
function
combineANDIn
(
query
,
types
)
{
const
newQuery
=
[]
const
newTypes
=
[]
for
(
let
i
=
0
;
i
<
query
.
length
;)
{
const
q0
=
query
[
i
-
1
];
const
t0
=
types
[
i
-
1
];
const
q1
=
query
[
i
];
const
t1
=
types
[
i
];
const
q2
=
query
[
i
+
1
];
const
t2
=
types
[
i
+
1
];
const
q3
=
query
[
i
+
2
];
const
t3
=
types
[
i
+
2
];
if
(((
t1
===
"
E
"
&&
t3
===
"
E
"
)
||
(
t1
===
"
F
"
&&
t3
===
"
F
"
))
&&
q2
===
"
AND
"
&&
q0
!==
"
NOT
"
)
{
let
active
=
true
let
type
=
t3
let
joined
=
[
q1
,
q3
]
i
=
i
+
3
while
(
active
)
{
const
q4
=
query
[
i
];
const
t4
=
types
[
i
];
const
q5
=
query
[
i
+
1
];
const
t5
=
types
[
i
+
1
];
if
(
q4
===
"
AND
"
&&
t5
===
type
)
{
joined
.
push
(
q5
)
i
+=
2
}
else
{
active
=
false
newQuery
.
push
(
joined
.
join
(
"
,
"
))
newTypes
.
push
(
type
)
}
}
}
else
{
newQuery
.
push
(
q1
)
newTypes
.
push
(
t1
)
i
+=
1
}
}
return
[
newQuery
,
newTypes
]
}
// For finding parenthesis pairs
function
findPairs
(
query
,
types
)
{
const
pairs
=
new
Map
();
// Find list of indices where parenthesis should be removed
let
removed
=
[]
for
(
let
i
=
0
;
i
<
query
.
length
;
++
i
)
{
const
q1
=
query
[
i
];
const
t1
=
types
[
i
];
if
(
q1
===
"
(
"
)
{
let
index
=
0
;
for
(
let
j
=
i
+
1
;
j
<
query
.
length
;
++
j
)
{
const
q2
=
query
[
j
];
const
t2
=
types
[
j
];
if
(
q2
===
"
(
"
)
{
index
+=
1
;
}
if
(
q2
===
"
)
"
)
{
if
(
index
!=
0
)
{
index
-=
1
;
}
else
{
pairs
.
set
(
i
,
j
)
pairs
.
set
(
j
,
i
)
break
}
}
}
}
}
return
pairs
;
}
// For removing unnecessary parenthesis
function
removeParenthesis
(
query
,
types
)
{