2 |
26 Feb 07 |
jari |
1 |
/* |
2 |
26 Feb 07 |
jari |
Copyright @ 1999-2003, The Institute for Genomic Research (TIGR). |
2 |
26 Feb 07 |
jari |
All rights reserved. |
2 |
26 Feb 07 |
jari |
4 |
*/ |
2 |
26 Feb 07 |
jari |
5 |
/* |
2 |
26 Feb 07 |
jari |
* $RCSfile: PermutationTest.java,v $ |
2 |
26 Feb 07 |
jari |
* $Revision: 1.4 $ |
2 |
26 Feb 07 |
jari |
* $Date: 2006/02/23 20:59:45 $ |
2 |
26 Feb 07 |
jari |
* $Author: caliente $ |
2 |
26 Feb 07 |
jari |
* $State: Exp $ |
2 |
26 Feb 07 |
jari |
11 |
*/ |
2 |
26 Feb 07 |
jari |
12 |
package org.tigr.microarray.mev.cluster.algorithm.impl; |
2 |
26 Feb 07 |
jari |
13 |
|
2 |
26 Feb 07 |
jari |
14 |
import java.util.Arrays; |
2 |
26 Feb 07 |
jari |
15 |
|
2 |
26 Feb 07 |
jari |
16 |
import org.tigr.microarray.mev.cluster.algorithm.AbortException; |
2 |
26 Feb 07 |
jari |
17 |
import org.tigr.microarray.mev.cluster.algorithm.AbstractAlgorithm; |
2 |
26 Feb 07 |
jari |
18 |
import org.tigr.microarray.mev.cluster.algorithm.AlgorithmData; |
2 |
26 Feb 07 |
jari |
19 |
import org.tigr.microarray.mev.cluster.algorithm.AlgorithmEvent; |
2 |
26 Feb 07 |
jari |
20 |
import org.tigr.microarray.mev.cluster.algorithm.AlgorithmException; |
2 |
26 Feb 07 |
jari |
21 |
import org.tigr.microarray.mev.cluster.algorithm.AlgorithmParameters; |
2 |
26 Feb 07 |
jari |
22 |
import org.tigr.microarray.mev.cluster.algorithm.impl.util.IntSorter; |
2 |
26 Feb 07 |
jari |
23 |
import org.tigr.util.FloatMatrix; |
2 |
26 Feb 07 |
jari |
24 |
|
2 |
26 Feb 07 |
jari |
25 |
public class PermutationTest extends AbstractAlgorithm { |
2 |
26 Feb 07 |
jari |
26 |
|
2 |
26 Feb 07 |
jari |
27 |
private static final int c_DecileCount = 10; |
2 |
26 Feb 07 |
jari |
28 |
public static final double LOG2 = Math.log(2.0); |
2 |
26 Feb 07 |
jari |
29 |
public static final float MINVAL = 0f; |
2 |
26 Feb 07 |
jari |
30 |
public static final float MAXVAL = 1f; |
2 |
26 Feb 07 |
jari |
31 |
|
2 |
26 Feb 07 |
jari |
32 |
private double getEntropy(float[] pVector) { |
2 |
26 Feb 07 |
jari |
33 |
double fltMin = Double.MAX_VALUE; |
2 |
26 Feb 07 |
jari |
34 |
double fltMax = -Double.MAX_VALUE; |
2 |
26 Feb 07 |
jari |
35 |
int i=0; |
2 |
26 Feb 07 |
jari |
36 |
int[] arrDeciles = new int[c_DecileCount]; |
2 |
26 Feb 07 |
jari |
37 |
|
2 |
26 Feb 07 |
jari |
38 |
final int iSize = pVector.length; |
2 |
26 Feb 07 |
jari |
39 |
int iValCount = 0; |
2 |
26 Feb 07 |
jari |
40 |
for (i=0; i<iSize; i++) { |
2 |
26 Feb 07 |
jari |
41 |
if (Double.isNaN(pVector[i])) |
2 |
26 Feb 07 |
jari |
42 |
continue; |
2 |
26 Feb 07 |
jari |
43 |
fltMin = Math.min(fltMin, pVector[i]); |
2 |
26 Feb 07 |
jari |
44 |
fltMax = Math.max(fltMax, pVector[i]); |
2 |
26 Feb 07 |
jari |
45 |
iValCount++; |
2 |
26 Feb 07 |
jari |
46 |
} |
2 |
26 Feb 07 |
jari |
47 |
|
2 |
26 Feb 07 |
jari |
48 |
double fltStep = (fltMax-fltMin)/(c_DecileCount); |
2 |
26 Feb 07 |
jari |
49 |
if (fltStep == 0d) { |
2 |
26 Feb 07 |
jari |
50 |
return -1.0*Math.log(1.0)/LOG2; |
2 |
26 Feb 07 |
jari |
51 |
} |
2 |
26 Feb 07 |
jari |
52 |
|
2 |
26 Feb 07 |
jari |
53 |
if (fltMin == Double.MAX_VALUE) |
2 |
26 Feb 07 |
jari |
54 |
return 0d; |
2 |
26 Feb 07 |
jari |
55 |
|
2 |
26 Feb 07 |
jari |
56 |
Arrays.fill(arrDeciles, 0); |
2 |
26 Feb 07 |
jari |
57 |
for (i=0; i<iSize; i++) { |
2 |
26 Feb 07 |
jari |
58 |
if (Double.isNaN(pVector[i])) |
2 |
26 Feb 07 |
jari |
59 |
continue; |
2 |
26 Feb 07 |
jari |
60 |
int iDecileInd = (int)Math.ceil((pVector[i]-fltMin)/fltStep)-1; |
2 |
26 Feb 07 |
jari |
61 |
if (iDecileInd < 0) { |
2 |
26 Feb 07 |
jari |
62 |
iDecileInd = 0; |
2 |
26 Feb 07 |
jari |
63 |
} |
2 |
26 Feb 07 |
jari |
64 |
arrDeciles[iDecileInd]++; |
2 |
26 Feb 07 |
jari |
65 |
} |
2 |
26 Feb 07 |
jari |
66 |
if (iValCount == 0) |
2 |
26 Feb 07 |
jari |
67 |
return 0d; |
2 |
26 Feb 07 |
jari |
68 |
|
2 |
26 Feb 07 |
jari |
// finally, calculate entropy |
2 |
26 Feb 07 |
jari |
70 |
double dblEntropy=0; |
2 |
26 Feb 07 |
jari |
71 |
|
2 |
26 Feb 07 |
jari |
72 |
for (i=0; i<c_DecileCount; i++) { |
2 |
26 Feb 07 |
jari |
73 |
if (arrDeciles[i] == 0) { |
2 |
26 Feb 07 |
jari |
74 |
continue; |
2 |
26 Feb 07 |
jari |
75 |
} |
2 |
26 Feb 07 |
jari |
76 |
double dblPx=((double)arrDeciles[i])/iValCount; |
2 |
26 Feb 07 |
jari |
77 |
dblEntropy += dblPx*Math.log(dblPx)/LOG2; // log2(x)==log(x)/log(2) |
2 |
26 Feb 07 |
jari |
78 |
} |
2 |
26 Feb 07 |
jari |
79 |
return -dblEntropy; |
2 |
26 Feb 07 |
jari |
80 |
} |
2 |
26 Feb 07 |
jari |
81 |
|
2 |
26 Feb 07 |
jari |
82 |
private boolean stop = false; |
2 |
26 Feb 07 |
jari |
83 |
|
2 |
26 Feb 07 |
jari |
84 |
private int[] m_arrMainHisto = null; // Real histo stores here |
2 |
26 Feb 07 |
jari |
//private int[][] m_arrPermHistos = null; // for every i-th permutaion m_PermHistos[i] stores appropriate histogram |
2 |
26 Feb 07 |
jari |
86 |
private double[] m_arrAvgHisto=null; |
2 |
26 Feb 07 |
jari |
87 |
private int m_iHistoSize = 0; |
2 |
26 Feb 07 |
jari |
88 |
private int m_iPermutationSize = 0; |
2 |
26 Feb 07 |
jari |
89 |
private float m_fltHistoStep = 0.5f; |
2 |
26 Feb 07 |
jari |
90 |
|
2 |
26 Feb 07 |
jari |
91 |
private float m_fltMaxStatSignificant = 0; |
2 |
26 Feb 07 |
jari |
92 |
|
2 |
26 Feb 07 |
jari |
93 |
private void Assert(int iFrom, int iTo, float fltVal) { |
2 |
26 Feb 07 |
jari |
94 |
m_arrMainHisto[GetIndByVal(fltVal)]++; |
2 |
26 Feb 07 |
jari |
95 |
} |
2 |
26 Feb 07 |
jari |
96 |
|
2 |
26 Feb 07 |
jari |
97 |
private void AssertPermutted(int iPermutationNum, int iFrom, int iTo, float fltVal) { |
2 |
26 Feb 07 |
jari |
//m_arrPermHistos[iPermutationNum][GetIndByVal(fltVal)]++; |
2 |
26 Feb 07 |
jari |
99 |
m_arrAvgHisto[GetIndByVal(fltVal)]++; |
2 |
26 Feb 07 |
jari |
100 |
m_fltMaxStatSignificant=Math.max(m_fltMaxStatSignificant, fltVal); |
2 |
26 Feb 07 |
jari |
101 |
} |
2 |
26 Feb 07 |
jari |
102 |
|
2 |
26 Feb 07 |
jari |
103 |
private int GetIndByVal(float fltVal) { |
2 |
26 Feb 07 |
jari |
104 |
int iDecileInd = (int)Math.ceil((fltVal-(MINVAL))/m_fltHistoStep)-1; |
2 |
26 Feb 07 |
jari |
105 |
if (iDecileInd < 0) |
2 |
26 Feb 07 |
jari |
106 |
return 0; |
2 |
26 Feb 07 |
jari |
107 |
return iDecileInd; |
2 |
26 Feb 07 |
jari |
108 |
} |
2 |
26 Feb 07 |
jari |
109 |
|
2 |
26 Feb 07 |
jari |
110 |
private void RandomPermute(FloatMatrix matr, int iRow) { |
2 |
26 Feb 07 |
jari |
111 |
int iFirstInd = 0; |
2 |
26 Feb 07 |
jari |
112 |
int iSecondInd =0; |
2 |
26 Feb 07 |
jari |
113 |
int iPermCount=(int)(Math.random()*matr.getColumnDimension()+1); |
2 |
26 Feb 07 |
jari |
114 |
for(int i=0; i<iPermCount;i++){ |
2 |
26 Feb 07 |
jari |
115 |
do{ |
2 |
26 Feb 07 |
jari |
116 |
iFirstInd = (int)(Math.random()*(matr.getColumnDimension()-1)); |
2 |
26 Feb 07 |
jari |
117 |
iSecondInd = (int)(Math.random()*(matr.getColumnDimension()-1)); |
2 |
26 Feb 07 |
jari |
118 |
}while(iFirstInd==iSecondInd); |
2 |
26 Feb 07 |
jari |
119 |
|
2 |
26 Feb 07 |
jari |
//System.out.println(iFirstInd); |
2 |
26 Feb 07 |
jari |
121 |
float temp=matr.A[iRow][iFirstInd]; |
2 |
26 Feb 07 |
jari |
122 |
matr.A[iRow][iFirstInd]=matr.A[iRow][iSecondInd]; |
2 |
26 Feb 07 |
jari |
123 |
matr.A[iRow][iSecondInd]=temp; |
2 |
26 Feb 07 |
jari |
124 |
} |
2 |
26 Feb 07 |
jari |
125 |
} |
2 |
26 Feb 07 |
jari |
126 |
|
2 |
26 Feb 07 |
jari |
127 |
private void Run(int[] entropyIndices, FloatMatrix expMatrix, int function, float factor, boolean absolute) throws AlgorithmException { |
2 |
26 Feb 07 |
jari |
128 |
int filteredSize = entropyIndices.length; |
2 |
26 Feb 07 |
jari |
129 |
int progress = 0; |
2 |
26 Feb 07 |
jari |
130 |
int links = 0; |
2 |
26 Feb 07 |
jari |
131 |
int sum = filteredSize*(filteredSize+1)/2; |
2 |
26 Feb 07 |
jari |
132 |
int step = sum/100+1; |
2 |
26 Feb 07 |
jari |
133 |
float value; |
2 |
26 Feb 07 |
jari |
134 |
AlgorithmEvent event = new AlgorithmEvent(this, AlgorithmEvent.SET_UNITS, 100, "Calculating Histogram"); |
2 |
26 Feb 07 |
jari |
135 |
fireValueChanged(event); |
2 |
26 Feb 07 |
jari |
136 |
|
2 |
26 Feb 07 |
jari |
137 |
event.setId(AlgorithmEvent.PROGRESS_VALUE); |
2 |
26 Feb 07 |
jari |
138 |
|
2 |
26 Feb 07 |
jari |
139 |
FloatMatrix temp= new FloatMatrix (1,expMatrix.getColumnDimension()); |
2 |
26 Feb 07 |
jari |
140 |
for (int nCurIndOuter=0; nCurIndOuter<filteredSize; nCurIndOuter++) { |
2 |
26 Feb 07 |
jari |
141 |
if (this.stop) { |
2 |
26 Feb 07 |
jari |
142 |
throw new AbortException(); |
2 |
26 Feb 07 |
jari |
143 |
} |
2 |
26 Feb 07 |
jari |
144 |
progress++; |
2 |
26 Feb 07 |
jari |
// calculation |
2 |
26 Feb 07 |
jari |
146 |
{ float[] arrX = expMatrix.A[entropyIndices[nCurIndOuter]]; |
2 |
26 Feb 07 |
jari |
147 |
float[] arrY = temp.A[0]; |
2 |
26 Feb 07 |
jari |
148 |
for (int k=0; k<arrY.length; k++)// copy outer vectr |
2 |
26 Feb 07 |
jari |
149 |
arrY[k]=arrX[k]; |
2 |
26 Feb 07 |
jari |
150 |
} |
2 |
26 Feb 07 |
jari |
151 |
|
2 |
26 Feb 07 |
jari |
152 |
for (int nCurIndInner=nCurIndOuter+1; nCurIndInner<filteredSize; nCurIndInner++) { |
2 |
26 Feb 07 |
jari |
//value = AlgorithmUtil.geneDistance(expMatrix, null, entropyIndices[nCurIndOuter], entropyIndices[nCurIndInner], function, factor, absolute); |
2 |
26 Feb 07 |
jari |
//value = value*value; // = abs(r^2) |
2 |
26 Feb 07 |
jari |
//value = AlgorithmUtil.genePearson(temp, expMatrix, 0, entropyIndices[nCurIndInner],1); |
2 |
26 Feb 07 |
jari |
//value*=value; |
2 |
26 Feb 07 |
jari |
157 |
|
2 |
26 Feb 07 |
jari |
//Assert(entropyIndices[nCurIndOuter], entropyIndices[nCurIndInner], value); |
2 |
26 Feb 07 |
jari |
159 |
|
2 |
26 Feb 07 |
jari |
160 |
for (int i=0; i<m_iPermutationSize; i++) { |
2 |
26 Feb 07 |
jari |
161 |
RandomPermute(temp, 0); |
2 |
26 Feb 07 |
jari |
//value = AlgorithmUtil.geneDistance(temp, expMatrix, 0, entropyIndices[nCurIndInner], function, factor, absolute); |
2 |
26 Feb 07 |
jari |
//value = value*value; // = abs(r^2) |
2 |
26 Feb 07 |
jari |
164 |
value = ExperimentUtil.genePearson(temp, expMatrix, 0, entropyIndices[nCurIndInner],1); |
2 |
26 Feb 07 |
jari |
165 |
value*=value; |
2 |
26 Feb 07 |
jari |
166 |
AssertPermutted(i, entropyIndices[nCurIndOuter], entropyIndices[nCurIndInner], value); |
2 |
26 Feb 07 |
jari |
167 |
} |
2 |
26 Feb 07 |
jari |
168 |
|
2 |
26 Feb 07 |
jari |
// progress events handling |
2 |
26 Feb 07 |
jari |
170 |
progress++; |
2 |
26 Feb 07 |
jari |
171 |
if (progress%step == 0) { |
2 |
26 Feb 07 |
jari |
172 |
event.setIntValue(progress/step); |
2 |
26 Feb 07 |
jari |
173 |
event.setDescription("Calculating permutation histogram "); |
2 |
26 Feb 07 |
jari |
174 |
fireValueChanged(event); |
2 |
26 Feb 07 |
jari |
175 |
} |
2 |
26 Feb 07 |
jari |
176 |
} |
2 |
26 Feb 07 |
jari |
177 |
} |
2 |
26 Feb 07 |
jari |
178 |
} |
2 |
26 Feb 07 |
jari |
179 |
|
2 |
26 Feb 07 |
jari |
180 |
public AlgorithmData execute(AlgorithmData data) throws AlgorithmException { |
2 |
26 Feb 07 |
jari |
181 |
|
2 |
26 Feb 07 |
jari |
182 |
FloatMatrix expMatrix = data.getMatrix("experiment"); |
2 |
26 Feb 07 |
jari |
183 |
if (expMatrix == null) |
2 |
26 Feb 07 |
jari |
184 |
return null; |
2 |
26 Feb 07 |
jari |
185 |
|
2 |
26 Feb 07 |
jari |
186 |
AlgorithmParameters map = data.getParams(); |
2 |
26 Feb 07 |
jari |
187 |
m_iHistoSize = map.getInt("decile-count", 1000); |
2 |
26 Feb 07 |
jari |
188 |
m_iPermutationSize = map.getInt("permutation-count", 20); |
2 |
26 Feb 07 |
jari |
189 |
m_fltHistoStep = (MAXVAL-MINVAL)/(float)m_iHistoSize; |
2 |
26 Feb 07 |
jari |
190 |
m_arrMainHisto = new int[m_iHistoSize]; |
2 |
26 Feb 07 |
jari |
191 |
m_arrAvgHisto=new double[m_iHistoSize]; |
2 |
26 Feb 07 |
jari |
//m_arrPermHistos = new int[m_iPermutationSize][]; |
2 |
26 Feb 07 |
jari |
//for (int i=0; i<m_iPermutationSize; i++) |
2 |
26 Feb 07 |
jari |
// m_arrPermHistos[i] = new int[m_iHistoSize]; |
2 |
26 Feb 07 |
jari |
195 |
|
2 |
26 Feb 07 |
jari |
196 |
int function = map.getInt("distance-function", PEARSON); |
2 |
26 Feb 07 |
jari |
197 |
float factor = map.getFloat("distance-factor", 1.0f); |
2 |
26 Feb 07 |
jari |
198 |
boolean absolute = map.getBoolean("distance-absolute", true); |
2 |
26 Feb 07 |
jari |
199 |
|
2 |
26 Feb 07 |
jari |
200 |
int nGenes = expMatrix.getRowDimension(); |
2 |
26 Feb 07 |
jari |
201 |
int filteredSize = nGenes; |
2 |
26 Feb 07 |
jari |
202 |
|
2 |
26 Feb 07 |
jari |
// For Entropy Filter |
2 |
26 Feb 07 |
jari |
204 |
boolean bFilterByEntropy = map.getBoolean("filter-by-entropy"); |
2 |
26 Feb 07 |
jari |
205 |
float fltTopNPercent=map.getFloat("top-n-percent", 100f); |
2 |
26 Feb 07 |
jari |
206 |
if (fltTopNPercent < 0f || fltTopNPercent>100f) { |
2 |
26 Feb 07 |
jari |
207 |
throw new AlgorithmException("Filter value is out of range (0, 100)%"); |
2 |
26 Feb 07 |
jari |
208 |
} |
2 |
26 Feb 07 |
jari |
209 |
|
2 |
26 Feb 07 |
jari |
210 |
int[] entropyIndices = new int[nGenes]; |
2 |
26 Feb 07 |
jari |
211 |
for (int i=0; i<entropyIndices.length; i++) { |
2 |
26 Feb 07 |
jari |
212 |
entropyIndices[i] = i; |
2 |
26 Feb 07 |
jari |
213 |
} |
2 |
26 Feb 07 |
jari |
214 |
|
2 |
26 Feb 07 |
jari |
215 |
if (bFilterByEntropy) { |
2 |
26 Feb 07 |
jari |
216 |
double[] entropyValues = new double[nGenes]; |
2 |
26 Feb 07 |
jari |
217 |
for (int i=0; i<entropyValues.length; i++) { |
2 |
26 Feb 07 |
jari |
218 |
entropyValues[i] = getEntropy(expMatrix.A[i]); |
2 |
26 Feb 07 |
jari |
219 |
} |
2 |
26 Feb 07 |
jari |
220 |
IntSorter.sort(entropyIndices, new RelNetComparator(entropyValues)); |
2 |
26 Feb 07 |
jari |
221 |
filteredSize = (int)((float)nGenes*fltTopNPercent/100f); |
2 |
26 Feb 07 |
jari |
222 |
} |
2 |
26 Feb 07 |
jari |
223 |
|
2 |
26 Feb 07 |
jari |
224 |
Run(entropyIndices, expMatrix, function, factor, absolute); |
2 |
26 Feb 07 |
jari |
225 |
|
2 |
26 Feb 07 |
jari |
// Retrieve threshold from multidimensional histogramms |
2 |
26 Feb 07 |
jari |
227 |
|
2 |
26 Feb 07 |
jari |
// make average histo |
2 |
26 Feb 07 |
jari |
// TODO: add min histo,max histo |
2 |
26 Feb 07 |
jari |
230 |
|
2 |
26 Feb 07 |
jari |
//for (int j=0; j<m_arrAvgHisto.length; j++) { |
2 |
26 Feb 07 |
jari |
// double fltAvg=0; |
2 |
26 Feb 07 |
jari |
//double fltMin=Double.MAX_VALUE; // never used in the future, but required for stdDev output in case of displayint a histogramm |
2 |
26 Feb 07 |
jari |
//double fltMax=-Double.MAX_VALUE;// never used in the future, but required for stdDev output in case of displayint a histogramm |
2 |
26 Feb 07 |
jari |
//for (int i=0; i<m_arrPermHistos.length; i++) { |
2 |
26 Feb 07 |
jari |
// fltAvg+=(double)(m_arrPermHistos[i][j])/(double)m_iPermutationSize; |
2 |
26 Feb 07 |
jari |
// fltMin=Math.min(fltMin, m_arrPermHistos[i][j]); |
2 |
26 Feb 07 |
jari |
// fltMax=Math.max(fltMax, m_arrPermHistos[i][j]); |
2 |
26 Feb 07 |
jari |
239 |
//} |
2 |
26 Feb 07 |
jari |
//m_arrAvgHisto[j]=fltAvg; |
2 |
26 Feb 07 |
jari |
241 |
//} |
2 |
26 Feb 07 |
jari |
242 |
|
2 |
26 Feb 07 |
jari |
243 |
float fltMinStatSignificantThreshold=m_fltMaxStatSignificant; |
2 |
26 Feb 07 |
jari |
244 |
if (fltMinStatSignificantThreshold>=1.0){//relaxed method(heuristic) |
2 |
26 Feb 07 |
jari |
245 |
for (int i=m_arrAvgHisto.length-1; i>0; i--) { |
2 |
26 Feb 07 |
jari |
//System.out.println("m_arrAvgHisto["+i+"]="+m_arrAvgHisto[i]); |
2 |
26 Feb 07 |
jari |
247 |
if (m_arrAvgHisto[i]/m_iPermutationSize>=1f ) { |
2 |
26 Feb 07 |
jari |
248 |
fltMinStatSignificantThreshold=(i+1)*m_fltHistoStep; |
2 |
26 Feb 07 |
jari |
249 |
if (fltMinStatSignificantThreshold>1f)// just in case |
2 |
26 Feb 07 |
jari |
250 |
fltMinStatSignificantThreshold=1f; |
2 |
26 Feb 07 |
jari |
251 |
break; |
2 |
26 Feb 07 |
jari |
252 |
} |
2 |
26 Feb 07 |
jari |
253 |
} |
2 |
26 Feb 07 |
jari |
254 |
} |
2 |
26 Feb 07 |
jari |
255 |
|
2 |
26 Feb 07 |
jari |
256 |
|
2 |
26 Feb 07 |
jari |
// return the result |
2 |
26 Feb 07 |
jari |
258 |
AlgorithmData result = new AlgorithmData(); |
2 |
26 Feb 07 |
jari |
259 |
result.addParam("threshold", String.valueOf(fltMinStatSignificantThreshold)); |
2 |
26 Feb 07 |
jari |
260 |
return result; |
2 |
26 Feb 07 |
jari |
261 |
} |
2 |
26 Feb 07 |
jari |
262 |
|
2 |
26 Feb 07 |
jari |
263 |
public void abort() { |
2 |
26 Feb 07 |
jari |
264 |
this.stop = true; |
2 |
26 Feb 07 |
jari |
265 |
} |
2 |
26 Feb 07 |
jari |
266 |
} |