@qudtlib/core
Advanced tools
Comparing version 3.1.0 to 3.1.1
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.arrayContains = exports.arrayMax = exports.arrayMin = exports.arrayCountEqualElements = exports.arrayEqualsIgnoreOrdering = exports.arrayEquals = exports.compareUsingEquals = exports.Qudt = exports.QUDT_PREFIX_BASE_IRI = exports.QUDT_QUANTITYKIND_BASE_IRI = exports.QUDT_UNIT_BASE_IRI = exports.Unit = exports.FactorUnits = exports.FactorUnit = exports.FactorUnitMatchingMode = exports.DerivedUnitSearchMode = exports.QuantityKind = exports.QuantityValue = exports.Prefix = exports.LangString = exports.config = exports.QudtlibConfig = exports.Decimal = void 0; | ||
exports.arrayContains = exports.arrayMax = exports.arrayMin = exports.arrayCountEqualElements = exports.arrayEqualsIgnoreOrdering = exports.arrayEquals = exports.compareUsingEquals = exports.Qudt = exports.QUDT_PREFIX_BASE_IRI = exports.QUDT_QUANTITYKIND_BASE_IRI = exports.QUDT_UNIT_BASE_IRI = exports.Unit = exports.FactorUnits = exports.FactorUnit = exports.FactorUnitMatchingMode = exports.DerivedUnitSearchMode = exports.QuantityKind = exports.QuantityValue = exports.Prefix = exports.LangString = exports.NaiveAlgorithmInstance = exports.Solution = exports.Instance = exports.AssignmentProblem = exports.config = exports.QudtlibConfig = exports.Decimal = void 0; | ||
const decimal_js_1 = require("decimal.js"); | ||
@@ -15,2 +15,159 @@ Object.defineProperty(exports, "Decimal", { enumerable: true, get: function () { return decimal_js_1.Decimal; } }); | ||
exports.config = new QudtlibConfig(); | ||
class AssignmentProblem { | ||
static instance(weights) { | ||
const rows = weights.length; | ||
if (!rows) { | ||
throw "Cannot create instance with 0x0 weights matrix"; | ||
} | ||
const cols = weights[0].length; | ||
if (rows > cols) { | ||
throw "The weights matrix may not have more rows than columns"; | ||
} | ||
return new NaiveAlgorithmInstance(AssignmentProblem.copy(weights)); | ||
} | ||
static copy(weights) { | ||
const ret = []; | ||
weights.forEach((row) => ret.push(row)); | ||
return ret; | ||
} | ||
} | ||
exports.AssignmentProblem = AssignmentProblem; | ||
class Instance { | ||
constructor(weights) { | ||
if (!weights?.length || !weights[0]?.length) { | ||
throw "Not a valid weights matrix: " + weights; | ||
} | ||
this.weights = weights; | ||
this.rows = weights.length; | ||
this.cols = weights[0].length; | ||
} | ||
weightOfAssignment(assignment) { | ||
if (!assignment) { | ||
throw "Not a valid assignment: " + assignment; | ||
} | ||
if (assignment?.length === 0) { | ||
return undefined; | ||
} | ||
const sum = assignment | ||
.map((col, row) => this.weights[row][col]) | ||
.reduce((a, b) => a + b); | ||
return sum; | ||
} | ||
} | ||
exports.Instance = Instance; | ||
class Solution { | ||
constructor(instance, assignment) { | ||
this.instance = instance; | ||
this.assignment = assignment || []; | ||
this.weight = instance.weightOfAssignment(this.assignment); | ||
} | ||
isComplete() { | ||
return this.assignment.length >= this.instance.rows; | ||
} | ||
isEmpty() { | ||
return this.assignment.length === 0; | ||
} | ||
assignColumnInNextRow(col) { | ||
if (this.isComplete()) { | ||
throw "Solution is already complete"; | ||
} | ||
return new Solution(this.instance, [...this.assignment, col]); | ||
} | ||
isBetterSolutionThan(other) { | ||
if (!(this.isComplete() && other.isComplete())) { | ||
throw "Cannot compare incomplete solutions"; | ||
} | ||
if (typeof this.weight === "undefined" || | ||
typeof other.weight === "undefined") { | ||
throw "Cannot compare empty solutions"; | ||
} | ||
return this.weight < other.weight; | ||
} | ||
} | ||
exports.Solution = Solution; | ||
class ValueWithIndex { | ||
constructor(value, index) { | ||
this.value = value; | ||
this.index = index; | ||
} | ||
} | ||
class NaiveAlgorithmInstance extends Instance { | ||
constructor(weights) { | ||
super(weights); | ||
} | ||
isLowerThanBestWeight(weightToTest) { | ||
if (!this.currentBestSolution) { | ||
return true; | ||
} | ||
if (!this.currentBestSolution.isComplete()) { | ||
return true; | ||
} | ||
if (!this.currentBestSolution.weight) { | ||
return true; | ||
} | ||
return this.currentBestSolution.weight > weightToTest; | ||
} | ||
updateBestSolutionIfPossible(candidate) { | ||
if (!this.currentBestSolution || | ||
candidate.isBetterSolutionThan(this.currentBestSolution)) { | ||
this.currentBestSolution = candidate; | ||
} | ||
} | ||
solve() { | ||
this.doSolve(0, new Solution(this)); | ||
if (!this.currentBestSolution) { | ||
return new Solution(this); | ||
} | ||
return this.currentBestSolution; | ||
} | ||
doSolve(row, solution) { | ||
if (row >= this.rows) { | ||
this.updateBestSolutionIfPossible(solution); | ||
return; | ||
} | ||
if (solution.weight) { | ||
const bestAttainableScore = this.sum(this.minPerRow(row, solution.assignment)); | ||
if (!this.isLowerThanBestWeight(solution.weight + bestAttainableScore)) { | ||
return; | ||
} | ||
} | ||
const nMin = this.rowSortedAscending(row, solution.assignment); | ||
for (let i = 0; i < nMin.length; i++) { | ||
if (!solution.weight || | ||
this.isLowerThanBestWeight(solution.weight + nMin[i].value)) { | ||
this.doSolve(row + 1, solution.assignColumnInNextRow(nMin[i].index)); | ||
} | ||
} | ||
} | ||
minPerRow(startRow, skipCols) { | ||
const ret = []; | ||
for (let r = startRow; r < this.rows; r++) { | ||
let min = Number.MAX_VALUE; | ||
for (let c = 0; c < this.cols; c++) { | ||
if (!arrayContains(skipCols, c)) { | ||
const val = this.weights[r][c]; | ||
if (min > val) { | ||
min = val; | ||
} | ||
} | ||
} | ||
ret.push(min); | ||
} | ||
return ret; | ||
} | ||
sum(arr) { | ||
return arr.reduce((a, b) => a + b); | ||
} | ||
rowSortedAscending(row, skipCols) { | ||
const sorted = []; | ||
for (let i = 0; i < this.cols; i++) { | ||
if (!arrayContains(skipCols, i)) { | ||
sorted.push(new ValueWithIndex(this.weights[row][i], i)); | ||
} | ||
} | ||
sorted.sort((l, r) => l.value - r.value); | ||
return sorted; | ||
} | ||
} | ||
exports.NaiveAlgorithmInstance = NaiveAlgorithmInstance; | ||
class LangString { | ||
@@ -278,4 +435,4 @@ constructor(text, languageTag) { | ||
getAllPossibleFactorUnitCombinations() { | ||
const leafFactorUnits = this.unit.getAllPossibleFactorUnitCombinations(); | ||
const result = leafFactorUnits.map((fus) => fus.map((fu) => fu.pow(this.exponent))); | ||
const subResult = this.unit.getAllPossibleFactorUnitCombinations(); | ||
const result = subResult.map((fus) => fus.map((fu) => fu.pow(this.exponent))); | ||
return arrayDeduplicate(result, (left, right) => arrayEqualsIgnoreOrdering(left, right, compareUsingEquals)); | ||
@@ -291,2 +448,3 @@ } | ||
const results = []; | ||
// cycle through all possible combinations of results per factor unit and combine them | ||
do { | ||
@@ -601,3 +759,6 @@ const curResult = []; | ||
if (!this.hasFactorUnits() || this.factorUnits.length === 0) { | ||
return [[new FactorUnit(this, 1)]]; | ||
if (!!this.scalingOf) { | ||
return this.scalingOf.getAllPossibleFactorUnitCombinations(); | ||
} | ||
return [[FactorUnit.ofUnit(this)]]; | ||
} | ||
@@ -858,8 +1019,21 @@ const result = FactorUnit.getAllPossibleFactorUnitCombinations(this.factorUnits); | ||
const requestedFactors = FactorUnit.getAllPossibleFactorUnitCombinations(requested); | ||
const overlap = arrayCountEqualElements(unitFactors, requestedFactors, (l, r) => arrayEqualsIgnoreOrdering(l, r, compareUsingEquals)); | ||
const overlapScore = overlap / (unitFactors.length + requestedFactors.length - overlap); | ||
const smaller = unitFactors.length < requestedFactors.length | ||
? unitFactors | ||
: requestedFactors; | ||
const larger = unitFactors.length < requestedFactors.length | ||
? requestedFactors | ||
: unitFactors; | ||
const unitSimilarityMatrix = Qudt.getUnitSimilarityMatrix(smaller, larger); | ||
let overlapScore = 0.0; | ||
if (unitSimilarityMatrix.length > 0) { | ||
overlapScore = this.getOverlapScore(unitSimilarityMatrix); | ||
} | ||
const unitLocalName = getLastIriElement(unit.iri); | ||
const tiebreaker = requested.reduce((prev, cur) => prev + | ||
(unitLocalName.match("\\b" + getLastIriElement(cur.unit.iri) + "\\b") != | ||
null | ||
null || | ||
unitLocalName.match("\\b" + | ||
getLastIriElement(cur.unit.iri) + | ||
Math.abs(cur.exponent) + | ||
"\\b") != null | ||
? 1 | ||
@@ -870,5 +1044,48 @@ : 0), 0); | ||
} | ||
static retainOnlyOne(matchingUnits) { | ||
return matchingUnits.reduce((p, n) => (p.iri > n.iri ? n : p)); | ||
static getUnitSimilarityMatrix(smaller, larger) { | ||
return smaller.map((sFactors) => larger.map((lFactors) => Qudt.scoreCombinations(sFactors, lFactors))); | ||
} | ||
static scoreCombinations(leftFactors, rightFactors) { | ||
const smaller = leftFactors.length < rightFactors.length ? leftFactors : rightFactors; | ||
const larger = leftFactors.length < rightFactors.length ? rightFactors : leftFactors; | ||
const similarityMatix = smaller.map((sFactor) => larger.map((lFactor) => { | ||
if (sFactor.equals(lFactor)) { | ||
return 0.0; | ||
} | ||
const sScalingOfOrSelf = sFactor.unit.scalingOf | ||
? sFactor.unit.scalingOf | ||
: sFactor.unit; | ||
const lScalingOfOrSelf = lFactor.unit.scalingOf | ||
? lFactor.unit.scalingOf | ||
: lFactor.unit; | ||
if (sFactor.exponent === lFactor.exponent && | ||
sScalingOfOrSelf.equals(lScalingOfOrSelf)) { | ||
return 0.6; | ||
} | ||
if (sFactor.unit.equals(lFactor.unit)) { | ||
return 0.8; | ||
} | ||
if (sScalingOfOrSelf.equals(lScalingOfOrSelf)) { | ||
return 0.9; | ||
} | ||
return 1.0; | ||
})); | ||
if (similarityMatix.length === 0) { | ||
return 1; | ||
} | ||
else { | ||
return 1 - Qudt.getOverlapScore(similarityMatix); | ||
} | ||
} | ||
static getOverlapScore(mat) { | ||
const numAssignments = mat.length; | ||
const instance = AssignmentProblem.instance(mat); | ||
const solution = instance.solve(); | ||
const minAssignmentScore = solution.isEmpty() | ||
? 1 * numAssignments | ||
: solution.weight; | ||
const overlap = numAssignments * (1 - minAssignmentScore / numAssignments); | ||
const rowsPlusCols = mat.length + mat[0].length; | ||
return overlap / (rowsPlusCols - overlap); | ||
} | ||
static findMatchingUnits(initialFactorUnitSelection) { | ||
@@ -875,0 +1092,0 @@ const matchingUnits = []; |
@@ -11,2 +11,155 @@ import { Decimal } from "decimal.js"; | ||
export const config = new QudtlibConfig(); | ||
export class AssignmentProblem { | ||
static instance(weights) { | ||
const rows = weights.length; | ||
if (!rows) { | ||
throw "Cannot create instance with 0x0 weights matrix"; | ||
} | ||
const cols = weights[0].length; | ||
if (rows > cols) { | ||
throw "The weights matrix may not have more rows than columns"; | ||
} | ||
return new NaiveAlgorithmInstance(AssignmentProblem.copy(weights)); | ||
} | ||
static copy(weights) { | ||
const ret = []; | ||
weights.forEach((row) => ret.push(row)); | ||
return ret; | ||
} | ||
} | ||
export class Instance { | ||
constructor(weights) { | ||
if (!weights?.length || !weights[0]?.length) { | ||
throw "Not a valid weights matrix: " + weights; | ||
} | ||
this.weights = weights; | ||
this.rows = weights.length; | ||
this.cols = weights[0].length; | ||
} | ||
weightOfAssignment(assignment) { | ||
if (!assignment) { | ||
throw "Not a valid assignment: " + assignment; | ||
} | ||
if (assignment?.length === 0) { | ||
return undefined; | ||
} | ||
const sum = assignment | ||
.map((col, row) => this.weights[row][col]) | ||
.reduce((a, b) => a + b); | ||
return sum; | ||
} | ||
} | ||
export class Solution { | ||
constructor(instance, assignment) { | ||
this.instance = instance; | ||
this.assignment = assignment || []; | ||
this.weight = instance.weightOfAssignment(this.assignment); | ||
} | ||
isComplete() { | ||
return this.assignment.length >= this.instance.rows; | ||
} | ||
isEmpty() { | ||
return this.assignment.length === 0; | ||
} | ||
assignColumnInNextRow(col) { | ||
if (this.isComplete()) { | ||
throw "Solution is already complete"; | ||
} | ||
return new Solution(this.instance, [...this.assignment, col]); | ||
} | ||
isBetterSolutionThan(other) { | ||
if (!(this.isComplete() && other.isComplete())) { | ||
throw "Cannot compare incomplete solutions"; | ||
} | ||
if (typeof this.weight === "undefined" || | ||
typeof other.weight === "undefined") { | ||
throw "Cannot compare empty solutions"; | ||
} | ||
return this.weight < other.weight; | ||
} | ||
} | ||
class ValueWithIndex { | ||
constructor(value, index) { | ||
this.value = value; | ||
this.index = index; | ||
} | ||
} | ||
export class NaiveAlgorithmInstance extends Instance { | ||
constructor(weights) { | ||
super(weights); | ||
} | ||
isLowerThanBestWeight(weightToTest) { | ||
if (!this.currentBestSolution) { | ||
return true; | ||
} | ||
if (!this.currentBestSolution.isComplete()) { | ||
return true; | ||
} | ||
if (!this.currentBestSolution.weight) { | ||
return true; | ||
} | ||
return this.currentBestSolution.weight > weightToTest; | ||
} | ||
updateBestSolutionIfPossible(candidate) { | ||
if (!this.currentBestSolution || | ||
candidate.isBetterSolutionThan(this.currentBestSolution)) { | ||
this.currentBestSolution = candidate; | ||
} | ||
} | ||
solve() { | ||
this.doSolve(0, new Solution(this)); | ||
if (!this.currentBestSolution) { | ||
return new Solution(this); | ||
} | ||
return this.currentBestSolution; | ||
} | ||
doSolve(row, solution) { | ||
if (row >= this.rows) { | ||
this.updateBestSolutionIfPossible(solution); | ||
return; | ||
} | ||
if (solution.weight) { | ||
const bestAttainableScore = this.sum(this.minPerRow(row, solution.assignment)); | ||
if (!this.isLowerThanBestWeight(solution.weight + bestAttainableScore)) { | ||
return; | ||
} | ||
} | ||
const nMin = this.rowSortedAscending(row, solution.assignment); | ||
for (let i = 0; i < nMin.length; i++) { | ||
if (!solution.weight || | ||
this.isLowerThanBestWeight(solution.weight + nMin[i].value)) { | ||
this.doSolve(row + 1, solution.assignColumnInNextRow(nMin[i].index)); | ||
} | ||
} | ||
} | ||
minPerRow(startRow, skipCols) { | ||
const ret = []; | ||
for (let r = startRow; r < this.rows; r++) { | ||
let min = Number.MAX_VALUE; | ||
for (let c = 0; c < this.cols; c++) { | ||
if (!arrayContains(skipCols, c)) { | ||
const val = this.weights[r][c]; | ||
if (min > val) { | ||
min = val; | ||
} | ||
} | ||
} | ||
ret.push(min); | ||
} | ||
return ret; | ||
} | ||
sum(arr) { | ||
return arr.reduce((a, b) => a + b); | ||
} | ||
rowSortedAscending(row, skipCols) { | ||
const sorted = []; | ||
for (let i = 0; i < this.cols; i++) { | ||
if (!arrayContains(skipCols, i)) { | ||
sorted.push(new ValueWithIndex(this.weights[row][i], i)); | ||
} | ||
} | ||
sorted.sort((l, r) => l.value - r.value); | ||
return sorted; | ||
} | ||
} | ||
export class LangString { | ||
@@ -270,4 +423,4 @@ constructor(text, languageTag) { | ||
getAllPossibleFactorUnitCombinations() { | ||
const leafFactorUnits = this.unit.getAllPossibleFactorUnitCombinations(); | ||
const result = leafFactorUnits.map((fus) => fus.map((fu) => fu.pow(this.exponent))); | ||
const subResult = this.unit.getAllPossibleFactorUnitCombinations(); | ||
const result = subResult.map((fus) => fus.map((fu) => fu.pow(this.exponent))); | ||
return arrayDeduplicate(result, (left, right) => arrayEqualsIgnoreOrdering(left, right, compareUsingEquals)); | ||
@@ -283,2 +436,3 @@ } | ||
const results = []; | ||
// cycle through all possible combinations of results per factor unit and combine them | ||
do { | ||
@@ -591,3 +745,6 @@ const curResult = []; | ||
if (!this.hasFactorUnits() || this.factorUnits.length === 0) { | ||
return [[new FactorUnit(this, 1)]]; | ||
if (!!this.scalingOf) { | ||
return this.scalingOf.getAllPossibleFactorUnitCombinations(); | ||
} | ||
return [[FactorUnit.ofUnit(this)]]; | ||
} | ||
@@ -847,8 +1004,21 @@ const result = FactorUnit.getAllPossibleFactorUnitCombinations(this.factorUnits); | ||
const requestedFactors = FactorUnit.getAllPossibleFactorUnitCombinations(requested); | ||
const overlap = arrayCountEqualElements(unitFactors, requestedFactors, (l, r) => arrayEqualsIgnoreOrdering(l, r, compareUsingEquals)); | ||
const overlapScore = overlap / (unitFactors.length + requestedFactors.length - overlap); | ||
const smaller = unitFactors.length < requestedFactors.length | ||
? unitFactors | ||
: requestedFactors; | ||
const larger = unitFactors.length < requestedFactors.length | ||
? requestedFactors | ||
: unitFactors; | ||
const unitSimilarityMatrix = Qudt.getUnitSimilarityMatrix(smaller, larger); | ||
let overlapScore = 0.0; | ||
if (unitSimilarityMatrix.length > 0) { | ||
overlapScore = this.getOverlapScore(unitSimilarityMatrix); | ||
} | ||
const unitLocalName = getLastIriElement(unit.iri); | ||
const tiebreaker = requested.reduce((prev, cur) => prev + | ||
(unitLocalName.match("\\b" + getLastIriElement(cur.unit.iri) + "\\b") != | ||
null | ||
null || | ||
unitLocalName.match("\\b" + | ||
getLastIriElement(cur.unit.iri) + | ||
Math.abs(cur.exponent) + | ||
"\\b") != null | ||
? 1 | ||
@@ -859,5 +1029,48 @@ : 0), 0); | ||
} | ||
static retainOnlyOne(matchingUnits) { | ||
return matchingUnits.reduce((p, n) => (p.iri > n.iri ? n : p)); | ||
static getUnitSimilarityMatrix(smaller, larger) { | ||
return smaller.map((sFactors) => larger.map((lFactors) => Qudt.scoreCombinations(sFactors, lFactors))); | ||
} | ||
static scoreCombinations(leftFactors, rightFactors) { | ||
const smaller = leftFactors.length < rightFactors.length ? leftFactors : rightFactors; | ||
const larger = leftFactors.length < rightFactors.length ? rightFactors : leftFactors; | ||
const similarityMatix = smaller.map((sFactor) => larger.map((lFactor) => { | ||
if (sFactor.equals(lFactor)) { | ||
return 0.0; | ||
} | ||
const sScalingOfOrSelf = sFactor.unit.scalingOf | ||
? sFactor.unit.scalingOf | ||
: sFactor.unit; | ||
const lScalingOfOrSelf = lFactor.unit.scalingOf | ||
? lFactor.unit.scalingOf | ||
: lFactor.unit; | ||
if (sFactor.exponent === lFactor.exponent && | ||
sScalingOfOrSelf.equals(lScalingOfOrSelf)) { | ||
return 0.6; | ||
} | ||
if (sFactor.unit.equals(lFactor.unit)) { | ||
return 0.8; | ||
} | ||
if (sScalingOfOrSelf.equals(lScalingOfOrSelf)) { | ||
return 0.9; | ||
} | ||
return 1.0; | ||
})); | ||
if (similarityMatix.length === 0) { | ||
return 1; | ||
} | ||
else { | ||
return 1 - Qudt.getOverlapScore(similarityMatix); | ||
} | ||
} | ||
static getOverlapScore(mat) { | ||
const numAssignments = mat.length; | ||
const instance = AssignmentProblem.instance(mat); | ||
const solution = instance.solve(); | ||
const minAssignmentScore = solution.isEmpty() | ||
? 1 * numAssignments | ||
: solution.weight; | ||
const overlap = numAssignments * (1 - minAssignmentScore / numAssignments); | ||
const rowsPlusCols = mat.length + mat[0].length; | ||
return overlap / (rowsPlusCols - overlap); | ||
} | ||
static findMatchingUnits(initialFactorUnitSelection) { | ||
@@ -864,0 +1077,0 @@ const matchingUnits = []; |
@@ -10,2 +10,35 @@ import { Decimal } from "decimal.js"; | ||
export declare const config: QudtlibConfig; | ||
export declare class AssignmentProblem { | ||
static instance(weights: number[][]): NaiveAlgorithmInstance; | ||
private static copy; | ||
} | ||
export declare abstract class Instance { | ||
readonly weights: number[][]; | ||
readonly rows: number; | ||
readonly cols: number; | ||
constructor(weights: number[][]); | ||
weightOfAssignment(assignment: number[]): number | undefined; | ||
abstract solve(): Solution; | ||
} | ||
export declare class Solution { | ||
readonly assignment: number[]; | ||
readonly weight: number | undefined; | ||
readonly instance: Instance; | ||
constructor(instance: Instance, assignment?: number[]); | ||
isComplete(): boolean; | ||
isEmpty(): boolean; | ||
assignColumnInNextRow(col: number): Solution; | ||
isBetterSolutionThan(other: Solution): boolean; | ||
} | ||
export declare class NaiveAlgorithmInstance extends Instance { | ||
private currentBestSolution?; | ||
constructor(weights: number[][]); | ||
isLowerThanBestWeight(weightToTest: number): boolean; | ||
updateBestSolutionIfPossible(candidate: Solution): void; | ||
solve(): Solution; | ||
doSolve(row: number, solution: Solution): void; | ||
private minPerRow; | ||
private sum; | ||
private rowSortedAscending; | ||
} | ||
export declare type UnitOrExponent = Unit | number; | ||
@@ -297,3 +330,5 @@ export declare type ExponentUnitPairs = UnitOrExponent[]; | ||
private static matchScore; | ||
private static retainOnlyOne; | ||
private static getUnitSimilarityMatrix; | ||
private static scoreCombinations; | ||
private static getOverlapScore; | ||
private static findMatchingUnits; | ||
@@ -300,0 +335,0 @@ /** |
{ | ||
"name": "@qudtlib/core", | ||
"version": "3.1.0", | ||
"version": "3.1.1", | ||
"description": "Data model for QUDTLib", | ||
@@ -55,3 +55,3 @@ "main": "dist/cjs/qudtlib.js", | ||
}, | ||
"gitHead": "cd6f450e0e6b421bcca624e4c48fabdac88b2478" | ||
"gitHead": "5df69026f4f1ffc59c340757c465211b10a950d7" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
380449
3158