//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 1.6 * @date Sun Nov 24 18:11:03 EST 2019 * @see LICENSE (MIT style license file). * * @see `TAScheduleTab` for the relation/table definitions */ package apps.database import scala.collection.mutable.{Set, Map, MutableList} import scala.math.{abs, round} import scalation.linalgebra.{MatrixD, VectorI, VectorS} import scalation.math.StrO._ import scalation.maxima.Hungarian import scalation.random.{Randi, Random} import scalation.util.{banner, BiMap} import TAScheduleTab._ //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TASchedule` object is an app used for scheduling TA assignments for the * next semester. * Only 1000, 2000, 3000, 4000, 6000 level courses qualify. *------------------------------------------------------------------------------ * It illustrates the capabilties of the ScalaTion analytics database. * > runMain apps.database.TASchedule */ object TASchedule { val DEBUG = true // debug flag val UNIT_HOURS = 40.0 * (2.0/9.0) // number of hours for one budget unit val MS_HOURS = 40.0 * (3.0/9.0) // number of hours for MS level TA val PHD_HOURS = 40.0 * (4.0/9.0) // number of hours for PhD level TA val LAB_HOURS = 1.5 // number of hours (one hour for lab + half hour prep) val P_MS = 0.33 // third at MS, two thirds at PhD private val rng = Random () // random number generator private val rig1 = Randi (1, 7) // random integer generator for TR periods private val rig2 = Randi (1, 10) // random integer generator for MWF periods private val rig3 = Randi (1, 3) // random integer generator for M, W or F private val ta_list = MutableList [String] () // teaching Assistant (TA) list of names val secMap = Map [Int, Double] () // section to TA effort (in hours) map val (cr, cn, le, p1, p2, in, sz, av) = (0, 1, 2, 3, 4, 5, 6, 7) // column names-numbers for section table val secMat = section.toMatriI (Seq (cr, cn, le, p1, p2, sz)) // section matrix val (crn_, cno_, lecture_, period1_, period2_, size_) = // column names-numbers for secMat matrix (0, 1, 2, 3, 4, 5) val cnoExclude = Set ("NONE-0000", "6950", "7000", "7005", "7007", "7100", "7200", "7300", "8990", "9000", "9005", "9300", "ARTI-8800") // no class periods assigned /** The period to class-time map * Code for MWF: 1 => M, 21 => W, 31 => F, 41 => MW, 51 => MWF * Code for TR: 71 => TR, 171 => T, 271 => R * Map: pid -> "days, period1, period2" * @see https://reg.uga.edu/general-information/daily-class-schedule */ val periodMap = Map (0 -> null, // none 1 -> " 800, 850", // MWF 2 -> " 905, 955", // MWF 3 -> " 1010, 1100", // MWF 4 -> " 1115, 1205", // MWF 5 -> " 1220, 1310", // MWF 6 -> " 1325, 1415", // MWF 7 -> " 1430, 1520", // MWF 8 -> " 1535, 1625", // MWF 9 -> " 1640, 1730", // MWF 10 -> " 1745, 1835", // MWF 71 -> " 800, 915", // TR 72 -> " 930, 1045", // TR 73 -> " 1100, 1215", // TR 74 -> " 1230, 1345", // TR 75 -> " 1400, 1515", // TR 76 -> " 1530, 1645", // TR 77 -> " 1700, 1815") // TR /** Time period column heading in TA schedule spreadsheet */ val periodList = "71,72,73,74,75,76,77,78,,1,2,3,4,5,6,7,8,9,10".split (",") //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Convert period code to period. * @param pcode the period code */ def pcode2Period (pcode: Int): (String, String) = { if (pcode > 280) ("??", s" pcode = $pcode") else if (pcode > 270) ("R", periodMap ((pcode - 1) % 100 + 1)) else if (pcode > 170) ("T", periodMap ((pcode - 1) % 100 + 1)) else if (pcode > 70) ("TR", periodMap (pcode)) else if (pcode > 50) ("MWF", periodMap ((pcode - 1) % 10 + 1)) else if (pcode > 40) ("MW", periodMap ((pcode - 1) % 10 + 1)) else if (pcode > 30) ("F", periodMap ((pcode - 1) % 10 + 1)) else if (pcode > 20) ("W", periodMap ((pcode - 1) % 10 + 1)) else ("M", periodMap (pcode)) } // pcode2Period //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Convert hours (of effort) to budget units. * @param hours the number of hours */ def hours2units (hours: Double): Double = { round (10.0 * hours / UNIT_HOURS) / 10.0 } // hours2units // Populate Tables //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Randomly populate the Teaching Assistant (ta) table. * This is for testing purposes. * @param numTAs the number of TAs * @param fracMS the fraction of TAs that are MS */ def populate_ta (numTAs: Int, fracPhD: Double) { for (i <- 0 until numTAs) { val ta_name = s"TA$i" ta_list += ta_name if (rng.gen < fracPhD) ta.insert (Vector (i, ta_name, 0, 0, MS_HOURS)) else ta.insert (Vector (i, ta_name, 1, 1, PHD_HOURS)) } // for ta.materialize () } // populate_ta //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Randomly generate a Tuesday and Thursday (TR) class period. */ def genTR: Int = 70 + rig1.igen //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Randomly generate a Monday, Wednesday or Friday class period. */ def genMWF: Int = { rig2.igen + (rig3.igen match { case 1 => 0 // Monday case 2 => 20 // Wednesday case 3 => 30 // Friday }) // match } // genMWF //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Randomly generate a Monday, Wednesday or Friday class period. */ def genCRN: Int = { val nCRN = secMat.dim1 val crn_gen = Randi (0, nCRN - 1) secMat(crn_gen.igen, 0) } // genCRN //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Randomly populate the Teaching Assistant class schedule (ta_sched) table. * This is for testing purposes. * @param numCourses the number of course taken per TA */ def populate_ta_sched (numCourses: Int) { for (i <- ta_list.indices) { for (j <- 1 to numCourses) { ta_sched.insert (Vector [Int] (i, genCRN)) } // for } // for ta_sched.materialize () } // populate_ta_sched //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Randomly populate the Teaching Assistant assignment (ta_assign) table. * This is for testing purposes. */ def populate_ta_assign () { val nTA = ta_list.size for (i <- ta_list.indices) { ta_assign.insert (Vector [Any] (i, secMat(i, crn_), UNIT_HOURS, secMat((i+1) % nTA, crn_), UNIT_HOURS)) } // for ta_assign.materialize () } // populate_ta_assign // Constraints //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Check to see whether the TA assignment satisfies the hard constraints, * returning true/false for the hard constraints (no violations allowed). *-------------------------------------------------------------------------- * Hard Constraints: * 1) no lab section time constraint * 2) TA not taking class they are TAing for * 3) filled total hours within 1 hour and no over-assignment * 4) filled total effort hours for section within 1 hour * 5) at least one TA assigned to a lab must have role_status = 1 */ def checkAssignment: Boolean = { var okay = true val ta_list = ta.select ("tname").toVectorS () for (i <- 0 until ta_list.dim) { // iterate over TAs val tname = ta_list(i).toString if (timeConflict (tname)) { println (s"checkAssignment: - TA with tname = $tname has a time conflict with their TA assignment") okay = false } // if /*** if (takingClass (tname)) { println (s"checkAssignment: - TA with tname = $tname is taking a class they are assigned to TA") okay = false } // if if (hoursWrong (tname)) { println (s"checkAssignment: - TA with tname = $tname is assigned the wrong number of hours") okay = false } // if ***/ } // for /*** for (i <- secMat.range1) { // iterate over sections if (effortWrong (secMat(i, crn_))) { println (s"checkAssignment: - section with crn = $i assigned the wrong amount of TA effort") okay = false } // if if (noLeadTA (secMat(i, crn_))) { println (s"checkAssignment: - section with crn = $i does not have a lead TA") okay = false } // if } // for ***/ okay } // checkAssignment //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Check to see whether the TA assignment satisfies the soft constraints, * returning a penalty score for the soft constraints (one point per violation). *-------------------------------------------------------------------------- * Soft Constraints: * 1) no lecture time conflict * 2) on instructor preferred list * 3) has taken the course they are TAing for */ def checkAssignment2: Int = { var score = 0 for (i <- ta_list.indices) { // iterate over TAs val tname = ta_list(i).toString if (lectureTimeConflict (tname)) { println (s"checkAssignment2: - TA with tname = $tname has a time conflict so they can't attend the lecture") score += 1 } // if if (notPreferred (tname)) { println (s"checkAssignment2: - TA with tname = $tname is not on the instructors preferred list") score += 1 } // if if (hasNotTaken (tname)) { println (s"checkAssignment2: - TA with tname = $tname has not taken the course they are TAing for") score += 1 } // if } // for score } // checkAssignment2 //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the TA has a time conflict between their TA assignment * and their enrolled classes. * FIX - differentiate from 'lectureTimeConflict'. * @param tname the unique name of the TA */ def timeConflict (tname: String): Boolean = { val ta_info = ta.where [StrNum] ("tname", _ == tname).select ("tname", "fname") .toVectorS () val taking = (ta_sched.where [StrNum] ("tname", _ == tname) join section).stack ("period1", "period2") val assigned = (ta_assign.where [StrNum] ("tname", _ == tname)) .join ("crn", "crn", section) // .where [Int] ("lecture", _ == 0)) .join ("tname", "tname", ta) .select ("tname", "fname", "cno", "crn", "hours", "iname", "period1", "period2") assigned.show () /*** if (assigned.exists) { val assigned_periods = assigned.stack ("period1", "period2").where [Int] ("period1", _ != 0) assigned_periods.show () val periods = (ta_sched.where [StrNum] ("tname", _ == tname)).select ("periods") .toVectorS () val periods_str_array = periods(0).toString.split (",", -1) val periods_seq = strPeriods2Periods (periods_str_array) println(periods_seq) } // if ***/ // if (DEBUG) { println (s"timeConflict: for tname = $tname"); taking.show (); assigned.show () } // (taking intersect2 assigned).exists false } // timeConflict //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the TA is enrolled in any class they are assigned as a TA. * @param tname the unique name of the TA */ def takingClass (tname: String): Boolean = { banner (s"takingClass check for $tname") val taking = ta_sched.where [StrNum] ("tname", _ == tname).select ("cnos") taking.show () val assigned = (ta_assign.where [StrNum] ("tname", _ == tname).join ("crn", "crn", section)).select ("cno") assigned.show () // if (DEBUG) { println (s"takingClass: for tname = $tname"); taking.show (); assigned.show () } // (taking intersect2 assigned).exists false } // takingClass //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the TA is assigned the wrong number of hours for their TAship * (either too few or too many hours). * @param tname the unique name of the TA */ def hoursWrong (tname: String): Boolean = { val effort = ta_assign.where [StrNum] ("tname", _ == tname).select ("hours").toVectorD ().sum val hours = ta.where [StrNum] ("tname", _ == tname).select ("hours").toVectorD ()(0) if (DEBUG) println (s"hoursWrong: for tname = $tname, effort = $effort, hours = $hours") effort > hours || hours - effort > 1.0 false } // hoursWrong //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the section is assigned the wrong amount of TA effort. * (either too few or too many hours). * @param crn the crn for a section */ def effortWrong (crn: Int): Boolean = { val effort = ta_assign.where [Int] ("crn1", _ == crn).stack ("hours1", "hours2").toVectorD ().sum + ta_assign.where [Int] ("crn2", _ == crn).stack ("hours1", "hours2").toVectorD ().sum val hours = secMap (crn) if (DEBUG) println (s"effortWrong: for crn = $crn, effort = $effort, hours = $hours") abs (effort - hours) > 1.0 } // effortwrong //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the lab section does not have at least one lead TA with 'role_status == 1'. * @param crn the crn for a lab section */ def noLeadTA (crn: Int): Boolean = { val lead = (ta join ta_assign.where [Int] ("crn1", _ == crn)).select ("role_status") union (ta join ta_assign.where [Int] ("crn2", _ == crn)).select ("role_status") if (DEBUG) println (s"noLeadTA: for lead = ${lead.toVectorI ()}") lead.toVectorI ().sum == 0 } // noLeadA //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the TA has a time conflict between the lecture for their TA * assignment and their enrolled classes, i.e, they will not be able to attend * the lecture they are TAing for. * FIX - differentiate from 'timeConflict'. * @param tname the id of the TA */ def lectureTimeConflict (tname: String): Boolean = { val taking = (ta_sched.where [StrNum] ("tname", _ == tname) join section).stack ("period1", "period2") val assigned = (ta_assign.where [StrNum] ("tname", _ == tname).stack ("crn1", "crn2")) .join ("crn1", "crn", section).stack ("period1", "period2") if (DEBUG) { println (s"lectureTimeConflict: for tname = $tname"); taking.show (); assigned.show () } (taking intersect2 assigned).exists } // lectureTimeConflict //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the TA is not on the instructors preferred list. * @param tname the unique name of the TA */ def notPreferred (tname: String): Boolean = { val tname_ = ta.where [StrNum] ("tname", _ == tname).select ("tname") val instructors = (ta_assign.where [StrNum] ("tname", _ == tname).stack ("crn1", "crn2")) .join ("crn1", "crn", section).select ("iname").toVectorS () for (iname <- instructors) { val preferred = i_pref.where [String] ("iname", _ == iname).stack ("tname1", "tname2") if (DEBUG) { println (s"notPreferred: for iname = $iname, tname = $tname is not preferred"); preferred.show () } if (! (tname_ intersect2 preferred).exists) return true } // for false } // notPreferred //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return whether the TA has not taken the course for which they are TAing. * @param tname the unique name of the TA */ def hasNotTaken (tname: String): Boolean = true // future enhancement /*** { val assigned = (ta_assign.where [StrNum] ("tname", _ == tname).stack ("crn1", "crn2")) .join ("crn1", "crn", section).select ("cno").toVectorI () for (cno <- assigned) { val taken = transcript.where [Int] ("tname", _ == tname).where [Int] ("cno", _ == cno) if (DEBUG) { println (s"hasNotTaken: for tname = $tname, cno = $cno"); taken.show () } if (! taken.exists) return true } // for false } // hasNotTaken ***/ // Utilities //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Check the integrity of the following tables: 'section', 'i_pref', 'ta', 'ta_sched'. * Must establish integrity before making TA assignments. */ def checkTables () { val faculty = section.select ("iname") val fac_pref = i_pref.select ("iname") val ta_pref = i_pref.stack ("tname1", "tname2").where [StrNum] ("tname1", _ != "") val ta_ior = ta.where [Int] ("level", _ == 2).select ("tname") val ta_gla = ta.where [Int] ("gla", _ == 1).select ("tname") banner ("instructors who are not faculty = ") val not_fac = faculty minus fac_pref not_fac.show () banner ("unknown instructor (not faculty or instructor of record (ior) TA = ") (not_fac minus ta_ior).show () banner ("faculty not teaching 6000- class = ") (fac_pref minus faculty).show () banner ("unknown perference given by faculty for TA = ") (ta_pref minus ta.select ("tname")).show () banner ("TA that's not gla/lab eligible = ") ta.where [Int] ("gla", _ == 0).show () val tas = ta_sched.select ("tname").toVectorS () val periods = ta_sched.select ("periods").toVectorS () val cnos = ta_sched.select ("cnos").toVectorS () val cnoSet = Set [String] () banner (s"check TA schedule format: comma separated list of course numbers - for ${tas.dim} TAs") println (s"periodList (${periodList.size}) = ${periodList.deep}") for (i <- 0 until tas.dim) { val name = tas(i).toString val gap = if (name.size < 10) "\t\t\t" else if (name.size < 17) "\t\t" else "\t" val cno_ = cnos(i).toString.split (",").map (_.trim) println (s"cno_ = ${cno_.deep}") cnoSet ++= cno_ banner (name + gap + cno_.deep) if (cno_.size > 5) println (s"--> $name registered for too many courses") val period_ = periods(i).toString.split (",", -1) println ("--- " + period_.size + " - " + period_.deep) if (period_.size != 1 && period_.size != 19) println ("--> error in entering periods") val pers0 = strPeriods2Periods (period_) val pers = courses2Periods (name) val diff1 = pers.toSet diff pers0.toSet val diff2 = pers0.toSet diff pers.toSet // println (s"pers0 = $pers0") // println (s"pers = ${pers.deep}") println (s"pers0.toSet = ${pers0.toSet}") // from spreadsheet println (s"pers.toSet = ${pers.toSet}") // from courses println (s"diff1 = $diff1") println (s"diff2 = $diff2") // pers.map (pcode2Period) // .foreach { case (d, t) => println(f"$d%2s $t") } } // for // println (s"cnoSet = ${cnoSet diff cnoExclude}") // banner ("TA able to cover labs (GLA) = ") // ta_gla.show () } // checkTables //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Convert the periods in string form into a sequence of periods (Ints). * Note: val periodList = "71,72,73,74,75,76,77,78,,1,2,3,4,5,6,7,8,9,10".split (",") * @param strPeriods the periods in string form */ def strPeriods2Periods (strPeriods: Array [String]): IndexedSeq [Int] = { val periods = strPeriods.map (_.trim) .map (_.replaceAll("/", "")) val codes = for (i <- 0 until periods.size - 1) yield { val code: Int = if (i < 8) { periods(i) match { case "TR" => 70 + i + 1 case "T" => 170 + i + 1 case "R" => 270 + i + 1 case _ => -1 } // match } else if (i > 8) { periods(i) match { case "M" => 0 + i - 8 case "W" => 20 + i - 8 case "F" => 30 + i - 8 case "MW" => 40 + i - 8 case "MWF" => 50 + i - 8 case _ => -2 } // match } else { -3 } // if // println(s"$i: $code: periods($i) = ${periods(i)}") code } // for // println(periods.deep) // println(codes) codes.filter(_ >= 0) } // strPeriods2Periods //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the periods that a TA is in class and not available for TA duties. * prefix -- BIOS 10, ETAP 20, GRSC 30, IDIS 40, LLED 50, PHIL 60, [skip UNIV 70 - 2900 has many sections] * @param tname the name of the TA */ def courses2Periods (tname: String): Array [Int] = { val cq = ta_sched.where [StrNum] ("tname", _ == tname).select ("cnos").toVectorS (0)(0) val ct = cq.toString.split (",").map (_.trim) println (s"ct = ${ct.deep}") val ca = for (cc <- ct if cc(0) != 'A' && cc(0) != 'U') yield { var c = cc if (c(0) == 'B') c = c.replace ("BIOS-", "10") if (c(0) == 'E') c = c.replace ("ETAP-", "20") if (c(0) == 'G') c = c.replace ("GRSC-", "30") if (c(0) == 'I') c = c.replace ("BIOS-", "40") if (c(0) == 'L') c = c.replace ("LLED-", "50") if (c(0) == 'M') c = c.replace ("MATH-", "60") if (c(0) == 'P') c = c.replace ("PHIL-", "60") // FIX - future - use different number val cn: Int = { try { java.lang.Integer.parseInt (c) } catch { case e : NumberFormatException => -1 } // try } // = cn } // for println (s"ca = ${ca.deep}") var pp = new VectorI (0) for (c <- ca) { val p = section.where [Int] ("cno", _ == c).stack ("period1", "period2").toVectorI () ++ section.where [Int] ("cno", _ == c - 2000).stack ("period1", "period2").toVectorI () ++ section2.where [Int] ("cno", _ == c).stack ("period1", "period2").toVectorI () println (s"c = $c: p = $p") pp = pp ++ p.filter(_ != 0) } // for pp.toArray } // courses2Periods //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Optimally populate the Teaching Assistant assignment (ta_assign) table. * All hard constriants are satisfied and penalty score for violating * soft contraints is minimized. * Alternative: prepare TA assignments using spreadsheet, load its csv file, * and call 'check_ta_assignment' check for constraint violations. */ def assign_ta () { // FIX - TBD later } // assign_ta //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Generate an index for each table. */ def genIndices () { section.repr.generateIndex () ta.repr.generateIndex () ta_sched.repr.generateIndex () i_pref.repr.generateIndex () ta_assign.repr.generateIndex () } // genIndices //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Show the tables. */ def showTables () { section.show () ta.show () ta_sched.show () i_pref.show () ta_assign.show () } // showTables } // TASchedule object import TASchedule._ //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TAScheduleSections` object shows the meeting times/periods for all sections. * > runMain apps.database.TAScheduleSections */ object TAScheduleSections extends App { banner ("TA Schedule Sections") println (s"periodMap = $periodMap") println ("-" * 74) println (" crn course lecture period1 period2") var cno = 0 // current course number var cno0 = 0 // previous course number for (i <- secMat.range1) { // iterate over all sections val secMati = secMat(i) // ith row of secMat matrix cno0 = cno // save previous course number cno = secMat(i, cno_) // get new course number if (cno != cno0) println ("-" * 74) val pc1 = pcode2Period (secMati(period1_)) // first period for crn (lecture/lab) val pc2 = pcode2Period (secMati(period2_)) // second period for crn (lab) print (s"${secMati(0 until period1_)}") print (s", \t $pc1") if (pc2._2 != null) print (s", \t $pc2") println () } // for } // TAScheduleSections object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TAScheduleEstimate` object tallies the total number of teaching assistant * hours required to estimate the number of TAs needed. * > runMain apps.database.TAScheduleEstimate */ object TAScheduleEstimate extends App { def tallyHours: Double = { var total = 0.0 // total TA hours var csum = 0.0 // TA hours for course var cno = 0 // current course number var cno0 = 0 // previous course number var crn = 0 // section crn for (i <- secMat.range1) { // iterate over all sections val secMati = secMat(i) // ith row of secMat matrix cno0 = cno // save previous course number cno = secMati(cno_) // get new course number crn = secMati(crn_) // get new course number if (cno != cno0) { println (s"course = $cno0, TA-hours = $csum, TA-units = ${hours2units (csum)}") csum = 0.0 // starting sections for new course => reset } // if val lec = secMati(lecture_) val big = secMati(size_) > 50 var sum = 0.0 if (lec == 1) { // section has main lecture sum += (if (big) PHD_HOURS else MS_HOURS) // grading if (cno == 1302) sum += 1 // cover mass exams in the evening // if (cno == 1730 || cno == 3030) sum += LAB_HOURS // lab with main lecture - now separated with pseudo crn } else { // lab sections val pc1 = secMati(period1_) val pc2 = secMati(period2_) sum += (if (pc1 > 20) 2 * LAB_HOURS // two labs lab-time + prep else LAB_HOURS) // one lab: lab-time + prep sum += (if (pc2 > 20) 2 * LAB_HOURS // two labs lab-time + prep else if (pc2 > 1) LAB_HOURS // one lab: lab-time + prep else 0.0) // no lab } // if // println (s"crn = ${secMati(crn_)}, cno = $cno, sum = $sum") secMap += crn -> sum csum += sum total += sum } // for println (s"course = $cno, TA-hours = $csum, TA-units = ${hours2units (csum)}") println (s"total = $total") total } // tallyHours banner ("TA Schedule Estimate") val total = tallyHours // estimate number of TA hours needed val avg = P_MS * MS_HOURS + (1 - P_MS) * PHD_HOURS // average hours per TA val nTAs = (total / avg).toInt // number of TAs needed val units = total / UNIT_HOURS // number of budget units needed println (s"UNIT_HOURS = $UNIT_HOURS") println (s"avg = $avg") println (s"nTAs = $nTAs") println (s"units = $units") } // TAScheduleEstimate object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TAScheduleCheck` object checks the integrity of the tables. * > runMain apps.database.TAScheduleCheck */ object TAScheduleCheck extends App { banner ("TA Schedule Perferences") genIndices () checkTables () } // TAScheduleCheck object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TAScheduleCheck2` object checks the TA assignment. * > runMain apps.database.TAScheduleCheck2 */ object TAScheduleCheck2 extends App { banner ("TA Schedule Time Conflict and Course Conflict") checkAssignment } // TAScheduleCheck2 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TAScheduleSimulate` object randomly populates the TA tables and checks * for constraints violations in the TA assignment. * Must set the number of TAs from departmental allocation or `TAScheduleEstimate`. * > runMain apps.database.TAScheduleSimulate */ object TAScheduleSimulate extends App { banner ("TA Schedule Simulate") val nTAs = 59 // the number of TAs banner (s"section matrix (secMat): rows = ${secMat.dim1}, cols = ${secMat.dim2}") println (secMat) populate_ta (nTAs, P_MS) populate_ta_sched (2) // 2 courses taken by each TA populate_ta_assign () genIndices () showTables () banner ("Check Hard Constraints") println (s"checkAssignment: $checkAssignment") banner ("Check Soft Constraints") println (s"checkAssignment2: $checkAssignment2") } // TAScheduleSimulate object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `FindTheTA` object finds the TA. * > runMain apps.database.FindTheTA */ object FindTheTA extends App { banner("FindTheTA") val tas = ta.where [Int] ("level", _ == 1) .where [Int] ("gla", _ == 1) .join ("tname", "tname", ta_assign) .where [Int] ("cno", _ != 13011) .where [Int] ("cno", _ != 21501) .where [Int] ("cno", _ != 11001) .join ("tname", "tname", ta_sched) .select ("tname", "fname", "cno", "crn", "hours", "periods", "cnos") val periods = Set(76, 48, 73, 41) tas.show() tas.where ("periods", (col: StrNum) => { val str_periods = col.toString.split (",", -1) val diff = strPeriods2Periods(str_periods).toSet diff periods diff.isEmpty }) .show () println(periods) } // FindTheTA //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `FindTheTA2` object finds the TA. * > runMain apps.database.FindTheTA */ object FindTheTA2 extends App { banner("FindTheTA") // crns for labs previously worked by Hill val crnos = Set(26347, 38860, 38861, 44514) val tas = ta.where [Int] ("gla", _ == 1) .where [Int] ("level", _ < 2) .join ("tname", "tname", ta_assign) .where [Int] ("cno", _ != 13011) .where [Int] ("cno", _ != 21501) .where [Int] ("cno", _ != 11001) .where [Int] ("cno", _ != 17302) .join ("tname", "tname", ta_sched) .select ("tname", "fname", "cno", "crn", "hours", "periods", "cnos") val periods = section.where [Int] ("crn", crnos contains _) .select ("period1") .toVectorI () .toSet tas.show() println(s"periods = $periods") val found = tas.where ("periods", (col: StrNum) => { val str_periods = col.toString.split (",", -1) val diff = strPeriods2Periods(str_periods).toSet diff periods diff.isEmpty }) found.show() val names = found.select ("tname") .toVectorS () .toSet println(names) import scalation.random.Randi val rng = new Randi(1, names.size) ta_assign.where [Int] ("cno", _ == 2720) .show () } // FindTheTA2 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TAScheduleCheck2` object find uassigned TAs. * > runMain apps.database.TAScheduleUnassigned */ object TAScheduleUnassigned extends App { banner ("TA Schedule Find Unassigned TAs") ta_assign.where [Int] ("cno", _ == 0).show () } // TAScheduleUnassigned object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `TAScheduleCheck2` object find uassigned TAs. * > runMain apps.database.TAScheduleCourseView */ object TAScheduleCourseView extends App { banner ("TA Schedule Course View") section.join ("crn", "crn", ta_assign.where [Int] ("cno", _ != 0)) .select ("cno", "crn", "iname", "size", "avail", "tname", "fname", "hours") .show () } // TAScheduleCourseView object