001 package ttsolver.heuristics;
002
003 import ifs.extension.*;
004 import ifs.heuristics.*;
005 import ifs.model.*;
006 import ifs.solution.*;
007 import ifs.solver.*;
008 import ifs.util.*;
009
010 import java.util.*;
011
012 import ttsolver.constraint.*;
013 import ttsolver.model.*;
014 import ttsolver.*;
015 import edu.purdue.smas.timetable.serverfwk.ParameterDefinition;
016
017 /**
018 * Lecture (variable) selection.
019 * <br><br>
020 * If there are one or more variables unassigned, the variable selection criterion picks one of them randomly. We have
021 * tried several approaches using domain sizes, number of previous assignments, numbers of constraints in which the
022 * variable participates, etc., but there was no significant improvement in this timetabling problem towards the random
023 * selection of an unassigned variable. The reason is, that it is easy to go back when a wrong variable is picked -
024 * such a variable is unassigned when there is a conflict with it in some of the subsequent iterations.
025 * <br><br>
026 * When all variables are assigned, an evaluation is made for each variable according to the above described weights. The
027 * variable with the worst evaluation is selected. This variable promises the best improvement in optimization.
028 * <br><br>
029 * Parameters (selection among unassigned lectures):
030 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
031 * <tr><td>Lecture.RouletteWheelSelection</td><td>{@link Boolean}</td><td>Roulette wheel selection</td></tr>
032 * <tr><td>Lecture.RandomWalkProb</td><td>{@link Double}</td><td>Random walk probability</td></tr>
033 * <tr><td>Lecture.DomainSizeWeight</td><td>{@link Double}</td><td>Domain size weight</td></tr>
034 * <tr><td>Lecture.NrAssignmentsWeight</td><td>{@link Double}</td><td>Number of assignments weight</td></tr>
035 * <tr><td>Lecture.InitialAssignmentWeight</td><td>{@link Double}</td><td>Initial assignment weight</td></tr>
036 * <tr><td>Lecture.NrConstraintsWeight</td><td>{@link Double}</td><td>Number of constraint weight</td></tr>
037 * </table>
038 * <br>
039 * Parameters (selection among assigned lectures, when the solution is complete):
040 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
041 * <tr><td>Lecture.HardStudentConflictWeight</td><td>{@link Double}</td><td>Hard student conflict weight</td></tr>
042 * <tr><td>Lecture.StudentConflictWeight</td><td>{@link Double}</td><td>Student conflict weight</td></tr>
043 * <tr><td>Lecture.TimePreferenceWeight</td><td>{@link Double}</td><td>Time preference weight</td></tr>
044 * <tr><td>Lecture.ContrPreferenceWeight</td><td>{@link Double}</td><td>Group constraint preference weight</td></tr>
045 * <tr><td>Lecture.RoomPreferenceWeight</td><td>{@link Double}</td><td>Room preference weight</td></tr>
046 * <tr><td>Lecture.UselessSlotWeight</td><td>{@link Double}</td><td>Useless slot weight</td></tr>
047 * <tr><td>Lecture.TooBigRoomWeight</td><td>{@link Double}</td><td>Too big room weight</td></tr>
048 * <tr><td>Lecture.DistanceInstructorPreferenceWeight</td><td>{@link Double}</td><td>Distance (of the rooms of the back-to-back classes) based instructor preferences weight</td></tr>
049 * <tr><td>Lecture.DeptSpreadPenaltyWeight</td><td>{@link Double}</td><td>Department balancing penalty (see {@link ttsolver.constraint.DepartmentSpreadConstraint})</td></tr>
050 * </table>
051 * <br>
052 * Parameters (selection among subset of lectures (faster)):
053 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
054 * <tr><td>Lecture.SelectionSubSet</td><td>{@link Boolean}</td><td>Selection among subset of lectures (faster)</td></tr>
055 * <tr><td>Lecture.SelectionSubSetMinSize</td><td>{@link Double}</td><td>Minimal subset size</td></tr>
056 * <tr><td>Lecture.SelectionSubSetPart</td><td>{@link Double}</td><td>Subset size in percentage of all lectures available for selection</td></tr>
057 * </table>
058 *
059 * @see PlacementSelection
060 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomáš Müller</a>
061 * @version 1.0
062 */
063 public class LectureSelection implements VariableSelection {
064 private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(LectureSelection.class);
065 private double iRandomWalkProb;
066 private double iDomainSizeWeight;
067 private double iGoodValuesWeight;
068 private double iNrAssignmentsWeight;
069 private double iConstraintsWeight;
070 private double iInitialAssignmentWeight;
071 private boolean iRouletteWheelSelection;
072 private boolean iUnassignWhenNotGood;
073
074 private double iEmptySingleSlotWeight;
075 private double iTooBigRoomWeight;
076 private double iTimePreferencesWeight;
077 private double iStudentConflictWeight;
078 private double iRoomPreferencesWeight;
079 private double iConstrPreferencesWeight;
080 private double iHardStudentConflictWeight;
081 private double iDistanceInstructorPreferenceWeight;
082 private double iDeptSpreadPenaltyWeight;
083
084 private boolean iSubSetSelection;
085 private double iSelectionSubSetPart;
086 private int iSelectionSubSetMinSize;
087
088 private boolean iRW = false;
089 private boolean iMPP = false;
090 private boolean iSwitchStudents = false;
091
092 private ConflictStatistics iStat = null;
093 private MacPropagation iProp = null;
094 private ViolatedInitials iViolatedInitials = null;
095
096 public LectureSelection(DataProperties properties) {
097 iRouletteWheelSelection = properties.getPropertyBoolean("Lecture.RouletteWheelSelection",true);
098 iUnassignWhenNotGood = properties.getPropertyBoolean("Lecture.UnassignWhenNotGood",false);
099 iRW = properties.getPropertyBoolean("General.RandomWalk", true);
100 iRandomWalkProb = (!iRW?0.0:properties.getPropertyDouble("Lecture.RandomWalkProb",1.00));
101 iGoodValuesWeight = properties.getPropertyDouble("Lecture.GoodValueProb",1.0);
102 iDomainSizeWeight = properties.getPropertyDouble("Lecture.DomainSizeWeight",30.0);
103
104 iNrAssignmentsWeight = properties.getPropertyDouble("Lecture.NrAssignmentsWeight",10.0);
105 iConstraintsWeight = properties.getPropertyDouble("Lecture.NrConstraintsWeight",0.0);
106 iMPP = properties.getPropertyBoolean("General.MPP", false);
107 iInitialAssignmentWeight = (!iMPP?0.0:properties.getPropertyDouble("Lecture.InitialAssignmentWeight",20.0));
108
109 iEmptySingleSlotWeight = properties.getPropertyDouble("Lecture.UselessSlotWeight",properties.getPropertyDouble("Comparator.UselessSlotWeight",0.0));
110 iTooBigRoomWeight = properties.getPropertyDouble("Lecture.TooBigRoomWeight",properties.getPropertyDouble("Comparator.TooBigRoomWeight",0.0));
111 iTimePreferencesWeight = properties.getPropertyDouble("Lecture.TimePreferenceWeight",properties.getPropertyDouble("Comparator.TimePreferenceWeight",1.0));
112 iStudentConflictWeight = properties.getPropertyDouble("Lecture.StudentConflictWeight",properties.getPropertyDouble("Comparator.StudentConflictWeight",0.2));
113 iRoomPreferencesWeight = properties.getPropertyDouble("Lecture.RoomPreferenceWeight",properties.getPropertyDouble("Comparator.RoomPreferenceWeight",0.1));
114 iConstrPreferencesWeight = properties.getPropertyDouble("Lecture.ContrPreferenceWeight",properties.getPropertyDouble("Comparator.ContrPreferenceWeight",1.0));
115
116 iSwitchStudents = properties.getPropertyBoolean("General.SwitchStudents",true);
117 iHardStudentConflictWeight = (!iSwitchStudents?0.0:properties.getPropertyDouble("Lecture.HardStudentConflictWeight",properties.getPropertyDouble("Comparator.HardStudentConflictWeight",1.0)));
118 iDistanceInstructorPreferenceWeight = properties.getPropertyDouble("Lecture.DistanceInstructorPreferenceWeight",properties.getPropertyDouble("Comparator.DistanceInstructorPreferenceWeight",1.0));
119 iDeptSpreadPenaltyWeight = properties.getPropertyDouble("Lecture.DeptSpreadPenaltyWeight",properties.getPropertyDouble("Comparator.DeptSpreadPenaltyWeight",1.0));
120
121 iSubSetSelection = properties.getPropertyBoolean("Lecture.SelectionSubSet",true);
122 iSelectionSubSetMinSize = properties.getPropertyInt("Lecture.SelectionSubSetMinSize",10);
123 iSelectionSubSetPart = properties.getPropertyDouble("Lecture.SelectionSubSetPart", 0.2);
124 }
125
126 public static Collection parameters() {
127 Vector ret = new FastVector();
128
129 //ParameterDefinition.Dependency mppDep = new ParameterDefinition.Dependency("General.MPP","true");
130 //ParameterDefinition.Dependency rwDep = new ParameterDefinition.Dependency("General.RandomWalk","true");
131 //ParameterDefinition.Dependency propDep = new ParameterDefinition.Dependency("General.MAC","true");
132 //ParameterDefinition.Dependency swDep = new ParameterDefinition.Dependency("General.SwitchStudents","true");
133 //ParameterDefinition.Dependency deptSpreadDep = new ParameterDefinition.Dependency("General.UseDepartmentSpreadConstraints","false");
134
135 //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.RouletteWheelSelection", "Roulette-wheel selection", ParameterDefinition.TYPE_BOOLEAN, "true"));
136 //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.UnassignWhenNotGood", "Unassign when no good", ParameterDefinition.TYPE_BOOLEAN, "false").addDependency(propDep));
137 //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.RandomWalkProb", "Random walk probability", ParameterDefinition.TYPE_DOUBLE, "0.02").addDependency(rwDep));
138 //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.GoodValueProb", "Good value selection", ParameterDefinition.TYPE_DOUBLE, "1.00").addDependency(propDep));
139
140 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.DomainSizeWeight", "Domain size", ParameterDefinition.TYPE_DOUBLE, "30.0"));
141 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.NrAssignmentsWeight", "Number of assignments", ParameterDefinition.TYPE_DOUBLE, "10.0"));
142 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.NrConstraintsWeight", "Number of constraints", ParameterDefinition.TYPE_DOUBLE, "0.0"));
143 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.InitialAssignmentWeight", "Initial assignment", ParameterDefinition.TYPE_DOUBLE, "20.0").addDependency(mppDep));
144
145 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.UselessSlotWeight", "Useless slots", ParameterDefinition.TYPE_DOUBLE, "0.00"));
146 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.TimePreferenceWeight", "Time preferences", ParameterDefinition.TYPE_DOUBLE, "1.00"));
147 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.StudentConflictWeight", "Student conflicts", ParameterDefinition.TYPE_DOUBLE, "0.10"));
148 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.RoomPreferenceWeight", "Room preferences", ParameterDefinition.TYPE_DOUBLE, "0.10"));
149 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.ContrPreferenceWeight", "Group constraint preferences", ParameterDefinition.TYPE_DOUBLE, "1.00"));
150 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.HardStudentConflictWeight", "Hard student conflicts", ParameterDefinition.TYPE_DOUBLE, "1.00"));
151
152 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.DistanceInstructorPreferenceWeight", "Distance Instructor Preference", ParameterDefinition.TYPE_DOUBLE, "0.10"));
153 //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.DeptSpreadPenaltyWeight", "Deparment balancing -- penalty a slot over initial allowance", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(deptSpreadDep));
154
155 //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.SelectionSubSet", "Subset selection", ParameterDefinition.TYPE_BOOLEAN, "true"));
156 //ParameterDefinition.Dependency subSetDep = new ParameterDefinition.Dependency("Lecture.SelectionSubSet", "true");
157 //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.SelectionSubSetMinSize", "Subset selection -- subset minimum size", ParameterDefinition.TYPE_INTEGER, "10").addDependency(subSetDep));
158 //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.SelectionSubSetPart", "Subset selection -- part", ParameterDefinition.TYPE_DOUBLE, "0.20").addDependency(subSetDep));
159
160 return ret;
161 }
162
163 public void init(Solver solver) {
164 for (Enumeration i=solver.getExtensions().elements();i.hasMoreElements();) {
165 Extension extension = (Extension)i.nextElement();
166 if (extension instanceof ConflictStatistics)
167 iStat = (ConflictStatistics) extension;
168 if (extension instanceof MacPropagation)
169 iProp = (MacPropagation)extension;
170 if (extension instanceof ViolatedInitials)
171 iViolatedInitials = (ViolatedInitials)extension;
172 }
173 }
174
175 public Variable selectVariable(Solution solution) {
176 if (solution.getModel().unassignedVariables().isEmpty()) {
177 Vector variables = solution.getModel().perturbVariables();
178 if (variables.isEmpty()) variables = solution.getModel().variables();
179
180 //if (iRW && ToolBox.random()<=iRandomWalkProb) return (Variable)ToolBox.random(variables);
181
182 Vector selectionVariables = null;
183 int worstTimePreference = 0;
184 int worstRoomConstrPreference = 0;
185
186 for (Iterator i1=(iSubSetSelection?ToolBox.subSet(variables,iSelectionSubSetPart,iSelectionSubSetMinSize):variables).iterator();i1.hasNext();) {
187 Variable selectedVariable = (Variable) i1.next();
188 Value value = (Value)selectedVariable.getAssignment();
189
190 int sumStudentConflicts = (iStudentConflictWeight!=0.0?((Lecture)selectedVariable).countStudentConflicts(value):0);
191 boolean haveAlternative = (((Lecture)selectedVariable).sameLectures()==null?false:((Lecture)selectedVariable).sameLectures().size()>1);
192 int constrPreference = 0;
193 int emptySingleHalfHours = 0;
194 int sumHardStudentConflicts = 0;
195 int distanceInstructorPreferences = 0;
196 boolean tooBig = ((Placement)value).getRoomLocation().getRoomSize()>TimetableModel.getMaxCapacity(((Lecture)selectedVariable).countStudents());
197
198 for (Enumeration i2=selectedVariable.constraints().elements();i2.hasMoreElements();) {
199 Constraint constraint = (Constraint)i2.nextElement();
200 if (iDistanceInstructorPreferenceWeight!=0.0 && constraint instanceof InstructorConstraint) {
201 distanceInstructorPreferences += ((InstructorConstraint)constraint).getPreference(value);
202 }
203 if (iHardStudentConflictWeight!=0.0 && !haveAlternative && constraint instanceof JenrlConstraint) {
204 JenrlConstraint jenrl = (JenrlConstraint)constraint;
205 Collection anotherSameLectures = ((Lecture)jenrl.another(selectedVariable)).sameLectures();
206 if (anotherSameLectures==null || anotherSameLectures.size()==1)
207 sumHardStudentConflicts += ((JenrlConstraint)constraint).jenrl(selectedVariable, value);
208 } else if (iConstrPreferencesWeight!=0.0 && constraint instanceof GroupConstraint) {
209 GroupConstraint gc = (GroupConstraint)constraint;
210 constrPreference += gc.getCurrentPreference();
211 } else if (iEmptySingleSlotWeight!=0.0 && constraint instanceof RoomConstraint) {
212 RoomConstraint rc = (RoomConstraint)constraint;
213 for (int i=0; i<((Placement)value).getTimeLocation().getStartSlots().length; i++) {
214 int startSlot = ((Placement)value).getTimeLocation().getStartSlots()[i];
215 int endSlot = startSlot + ((Placement)value).getTimeLocation().getLength() - 1;
216 if (((startSlot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)>=2) && rc.getResource()[startSlot-1]==null && rc.getResource()[startSlot-2]!=null)
217 emptySingleHalfHours++;
218 if (((endSlot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)<edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY-2) && rc.getResource()[startSlot+1]==null && rc.getResource()[startSlot+2]!=null)
219 emptySingleHalfHours++;
220 }
221 }
222 }
223
224 int roomPreference = ((Placement)value).getRoomLocation().getPreference();
225 double deptSpreadPenalty = (iDeptSpreadPenaltyWeight==0.0 || ((Lecture)selectedVariable).getDeptSpreadConstraint()==null?0:((Lecture)selectedVariable).getDeptSpreadConstraint().getPenalty());
226 int timePreference = (int)(100.0*(
227 (iStudentConflictWeight*sumStudentConflicts)+
228 (iHardStudentConflictWeight*sumHardStudentConflicts)+
229 (iTimePreferencesWeight*((Placement)value).getTimeLocation().getNormalizedPreference())+
230 (iEmptySingleSlotWeight*emptySingleHalfHours)+
231 (iRoomPreferencesWeight*roomPreference)+
232 (tooBig?iTooBigRoomWeight:0.0)+
233 (iConstrPreferencesWeight*constrPreference)+
234 (iDistanceInstructorPreferenceWeight*distanceInstructorPreferences)+
235 (iDeptSpreadPenaltyWeight*deptSpreadPenalty)
236 ));
237
238 if (selectionVariables==null || timePreference>worstTimePreference || (timePreference==worstTimePreference && roomPreference+constrPreference>worstRoomConstrPreference)) {
239 if (selectionVariables==null) selectionVariables=new FastVector(); else selectionVariables.clear();
240 selectionVariables.addElement(selectedVariable);
241 worstTimePreference=timePreference;
242 worstRoomConstrPreference=roomPreference+constrPreference;
243 } else if (timePreference==worstTimePreference && roomPreference+constrPreference==worstRoomConstrPreference) {
244 selectionVariables.addElement(selectedVariable);
245 }
246 }
247
248 return (Variable)ToolBox.random(selectionVariables);
249 } else {
250 if (ToolBox.random()<=iRandomWalkProb) return (Variable)ToolBox.random(solution.getModel().unassignedVariables());
251
252 if (iProp!=null && iUnassignWhenNotGood) {
253 int totalPoints = 0;
254 Vector noGoodVariables = new FastVector();
255 for (Iterator i1=ToolBox.subSet(solution.getModel().unassignedVariables(),iSelectionSubSetPart,iSelectionSubSetMinSize).iterator();i1.hasNext(); ){
256 Variable variable = (Variable) i1.next();
257 if (iProp.goodValues(variable).isEmpty())
258 noGoodVariables.addElement(variable);
259 }
260 if (!noGoodVariables.isEmpty()) {
261 if (ToolBox.random()<0.02) return (Variable)ToolBox.random(solution.getModel().assignedVariables());
262 for (int attempt=0;attempt<10;attempt++) {
263 Variable noGoodVariable = (Variable)ToolBox.random(noGoodVariables);
264 Value noGoodValue = (Value)ToolBox.random(noGoodVariable.values());
265 if (!iProp.noGood(noGoodValue).isEmpty()) return ((Value)ToolBox.random(iProp.noGood(noGoodValue))).variable();
266 }
267 }
268 }
269
270 if (iRouletteWheelSelection) {
271 int iMaxDomainSize=0;
272 int iMaxGoodDomainSize=0;
273 int iMaxConstraints=0;
274 long iMaxNrAssignments=0;
275 Collection variables = (iSubSetSelection?ToolBox.subSet(solution.getModel().unassignedVariables(),iSelectionSubSetPart,iSelectionSubSetMinSize):solution.getModel().unassignedVariables());
276 for (Iterator i=variables.iterator();i.hasNext(); ){
277 Variable variable = (Variable) i.next();
278 iMaxDomainSize=Math.max(iMaxDomainSize,variable.values().size());
279 iMaxGoodDomainSize=(iProp==null?0:Math.max(iMaxGoodDomainSize,iProp.goodValues(variable).size()));
280 iMaxConstraints=Math.max(iMaxConstraints,variable.constraints().size());
281 iMaxNrAssignments=Math.max(iMaxNrAssignments,variable.countAssignments());
282 }
283
284 Vector points = new FastVector();
285 int totalPoints = 0;
286
287 for (Iterator i=variables.iterator();i.hasNext(); ){
288 Variable variable = (Variable) i.next();
289
290 long pointsThisVariable = Math.round(
291 iDomainSizeWeight*(((double)(iMaxDomainSize-variable.values().size()))/((double)iMaxDomainSize))+
292 (iProp==null?0.0:iGoodValuesWeight*(((double)(iMaxGoodDomainSize-iProp.goodValues(variable).size()))/((double)iMaxGoodDomainSize)))+
293 iNrAssignmentsWeight*(((double)variable.countAssignments())/((double)iMaxNrAssignments))+
294 iConstraintsWeight*(((double)(iMaxConstraints-variable.constraints().size()))/((double)iMaxConstraints))+
295 iInitialAssignmentWeight*(variable.getInitialAssignment()!=null?solution.getModel().conflictValues(variable.getInitialAssignment()).size():0.0)
296 );
297 if (pointsThisVariable>0) {
298 totalPoints += pointsThisVariable;
299 points.addElement(new Integer(totalPoints));
300 }
301 }
302
303 if (totalPoints>0) {
304 int rndPoints = ToolBox.random(totalPoints);
305 Iterator x=variables.iterator();
306 for (int i=0;x.hasNext() && i<points.size();i++){
307 Variable variable = (Variable)x.next();
308 int tp = ((Integer)points.elementAt(i)).intValue();
309 if (tp>rndPoints) return variable;
310 }
311 }
312
313 } else {
314
315 Vector selectionVariables = null;
316 long bestGood = 0;
317 for (Iterator i=ToolBox.subSet(solution.getModel().unassignedVariables(),iSelectionSubSetPart,iSelectionSubSetMinSize).iterator();i.hasNext(); ){
318 Variable variable = (Variable) i.next();
319
320 long good = (long)(
321 iDomainSizeWeight*variable.values().size()
322 + iGoodValuesWeight*(iProp==null?0:iProp.goodValues(variable).size())
323 + iNrAssignmentsWeight*variable.countAssignments()
324 + iConstraintsWeight*variable.constraints().size()
325 + iInitialAssignmentWeight*(variable.getInitialAssignment()!=null?solution.getModel().conflictValues(variable.getInitialAssignment()).size():0.0)
326 );
327 if (selectionVariables==null || bestGood>good) {
328 if (selectionVariables==null) selectionVariables = new FastVector(); else selectionVariables.clear();
329 bestGood=good;
330 selectionVariables.addElement(variable);
331 } else if (good==bestGood) {
332 selectionVariables.addElement(variable);
333 }
334 }
335
336 if (!selectionVariables.isEmpty())
337 return (Variable)ToolBox.random(selectionVariables);
338 }
339 return (Variable)ToolBox.random(solution.getModel().unassignedVariables());
340 }
341 }
342
343 }