Commit 14879256 authored by Iovka Boneva's avatar Iovka Boneva
Browse files

simplify structure

parent 4aa822ae
......@@ -17,7 +17,7 @@
* along with Grapp. If not, see <https://www.gnu.org/licenses/>
*/
package fr.ulille.grapp.exercises.formatting
package fr.ulille.grapp.exercises
import java.lang.StringBuilder
......@@ -41,7 +41,7 @@ object ClozeFormatter {
pointsPerQuestion: Int,
indicesOfLinesThatAreNotQuestions : Set<Int> = emptySet(),
namesOfColumnsThatAreNotQuestions: Set<String> = emptySet()) {
s.append("<table ${clozeTableStyle}>\n")
s.append("<table $clozeTableStyle>\n")
clozeTableHeader(s, columnNames)
s.append("<tbody>\n")
for (lineIndex in table.indices) {
......@@ -52,7 +52,7 @@ object ClozeFormatter {
val valueString =
if (indicesOfLinesThatAreNotQuestions.contains(lineIndex) || namesOfColumnsThatAreNotQuestions.contains(colName)) value
else clozeQuestion(value, pointsPerQuestion)
s.append("<td ${clozeTdStyle}>$valueString</td>\n")
s.append("<td $clozeTdStyle>$valueString</td>\n")
}
s.append("</tr>\n")
}
......@@ -63,7 +63,7 @@ object ClozeFormatter {
private fun clozeTableHeader (s: StringBuilder, columnNames: List<String>) {
s.append("<thead><tr>\n")
for (cname in columnNames) {
s.append("<th ${clozeThStyle}>$cname</th>\n")
s.append("<th $clozeThStyle>$cname</th>\n")
}
s.append("</tr></thead>\n")
}
......
......@@ -22,17 +22,14 @@ package fr.ulille.grapp.exercises
import fr.ulille.grapp.algorithms.Dijkstra
import fr.ulille.grapp.algorithms.traces.DijkstraTraces
import fr.ulille.grapp.algorithms.traces.ExecutionTrace
import fr.ulille.grapp.exercises.formatting.ClozeFormatter
import fr.ulille.grapp.exercises.formatting.GraphOnGridTikzFormatter
import fr.ulille.grapp.exercises.gen.GridGraphGenerator
import fr.ulille.grapp.graph.Graph
import fr.ulille.grapp.graph.GrappType
import fr.ulille.grapp.graph.Vertex
import fr.ulille.grapp.graph.*
import fr.ulille.grapp.io.DotIo
import fr.ulille.grapp.io.LatexSerializer
import fr.ulille.grapp.io.Util
import fr.ulille.grapp.ui.*
import org.jgrapht.alg.shortestpath.BFSShortestPath
import java.io.File
import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
class DijkstraExercise (private val outputFolderName: String, private val graphName: String, private val isDirected: Boolean) {
......@@ -226,3 +223,204 @@ ${predecessorsQuestion(graph, pred, start, pointsPred)}
}
}
}
typealias NodeCoord = Pair<Int,Int>
typealias EdgeCoord = Pair<NodeCoord, NodeCoord>
fun NodeCoord.x() = first
fun NodeCoord.y() = second
fun EdgeCoord.src() = first
fun EdgeCoord.tgt() = second
/** Allows to generate a graph which nodes are letters "a", "b", ... and are arranged on a grid. */
class GridGraphGenerator (val nbNodeLines : Int = 3,
val nbNodeColumns : Int = 3,
excluedNodes : Set<Vertex> = setOf()) {
private val xrange = IntRange(0, nbNodeLines-1)
private val yrange = IntRange(0, nbNodeColumns-1)
private val nodesSet = CharRange('a', 'a' + (nbNodeLines * nbNodeColumns - 1))
.map { it.toString() }
.minus(excluedNodes)
private fun toNode(nodeCoord: NodeCoord): String {
return "${('a'.code + (nodeCoord.x() * nbNodeColumns + nodeCoord.y())).toChar()}"
}
private fun toCoordinates (node: Vertex) : NodeCoord {
val nodeRank = (node[0] - 'a')
val x = nodeRank / nbNodeColumns
val y = nodeRank % nbNodeColumns
return Pair(x,y)
}
private fun outgoingEdges (node: NodeCoord) : List<EdgeCoord> {
val result = mutableListOf<EdgeCoord>()
val (x,y) = node
for (i in -1 .. 1)
for (j in -1 .. 1)
if ((i!=0 || j!=0) && x+i in xrange && y+j in yrange) {
val targetNodeCoord = NodeCoord(x + i, y + j)
if (toNode(targetNodeCoord) in nodesSet) // TODO : test si le sommet fait partie du graphe
result.add(EdgeCoord(node, targetNodeCoord))
}
return result
}
fun generateGraph (graphName: String, nbEdges: Int, grappType: GrappType) : Graph {
// TODO ceci n'est pas valide si on exclut des sommets
val maxPossibleNbEdges = nbNodeColumns*(nbNodeLines-1) + (nbNodeColumns-1)*nbNodeLines + (nbNodeColumns-1)*(nbNodeLines-1)
if (nbEdges > maxPossibleNbEdges)
throw IllegalArgumentException("Impossible to generate a graph with as many edges.")
val allPossibleEdges = mutableSetOf<EdgeCoord>()
for (node in nodesSet)
allPossibleEdges.addAll(outgoingEdges(toCoordinates(node)))
val edges = mutableListOf<EdgeCoord>()
while (edges.size != nbEdges) {
val edge = allPossibleEdges.random()
allPossibleEdges.remove(edge)
if (canBeAdded(edge, edges))
edges.add(edge)
}
val values = if (grappType.isWeighted()) (1..nbEdges).map {it.toDouble()}.toMutableSet() else mutableSetOf()
val randomValue : () -> Double = {
val result = values.random()
values.remove(result)
result
}
val graph = Graphs.graphFromNodeSet(grappType, nodesSet.map{ "$it" }.toList())
graph.setName(graphName)
for (edge in edges) {
if (grappType.isWeighted())
Graphs.addEdge(graph, toNode(edge.src()), toNode(edge.tgt()), randomValue())
else
Graphs.addEdge(graph, toNode(edge.src()), toNode(edge.tgt()))
}
return graph
}
fun randomVertex () : Vertex {
return nodesSet.random().toString()
}
fun firstVertex () : Vertex {
return nodesSet.first()
}
companion object {
private fun canBeAdded (edge: EdgeCoord, to: Collection<EdgeCoord>) : Boolean {
val isOpposite : (EdgeCoord, EdgeCoord) -> Boolean = { e1, e2 ->
e1.src() == e2.tgt() && e1.tgt() == e2.src()
}
for (e in to)
if (e == edge || isOpposite(edge,e) || areCrossing(edge,e))
return false
return true
}
private fun areCrossing (edge: EdgeCoord, anotherEdge: EdgeCoord) : Boolean {
val topToBottom : (EdgeCoord) -> EdgeCoord = { e ->
if (e.src().x() > e.tgt().x())
Pair(e.tgt(),e.src())
else
Pair(e.src(), e.tgt())
}
val isDiagonalLeftToRight : (EdgeCoord) -> Boolean = { e ->
e.src().x() + 1 == e.tgt().x() && e.src().y() + 1 == e.tgt().y()
}
val isDiagonalRightToLeft : (EdgeCoord) -> Boolean = { e ->
e.src().x() + 1 == e.tgt().x() && e.src().y() == e.tgt().y() + 1
}
val sourcesAreSameLineNeighboursLeftAndRight : (EdgeCoord, EdgeCoord) -> Boolean = { e1, e2 ->
e1.src().x() == e2.src().x() && e1.src().y() + 1 == e2.src().y()
}
val edge = topToBottom(edge)
val anotherEdge = topToBottom(anotherEdge)
return sourcesAreSameLineNeighboursLeftAndRight(edge, anotherEdge) &&
isDiagonalLeftToRight(edge) && isDiagonalRightToLeft(anotherEdge)
||
sourcesAreSameLineNeighboursLeftAndRight(anotherEdge, edge) &&
isDiagonalLeftToRight(anotherEdge) && isDiagonalRightToLeft(edge)
}
}
}
object GraphOnGridTikzFormatter {
fun standaloneTikzPicture (graph: Graph,
drawInstructions : Map<String, String>,
edgeLabel: (Edge) -> String = LatexSerializer.defaultEdgeLabel(graph.isWeighted())) : String {
val s = StringBuilder()
s.append(
"""
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{arrows,automata,chains,matrix,positioning,scopes,arrows.meta}
\begin{document}
"""
)
graph.setDrawInstructions(drawInstructions)
LatexSerializer.serializeToTikz(graph, s, false, edgeLabel)
s.appendLine("\\end{document}")
return s.toString()
}
/** Pour graphe normal ou réseau de transport avec source 'o' et puits 'p' */
val o_abc_def_ghi_p_drawInstructions = mutableMapOf<String,String>().apply {
this["o"] = "-1,-1"
this["p"] = "3,-1"
for (c in 'a' .. 'i') {
val rank = c - 'a'
this["$c"] = "${rank % 3},${-rank / 3}"
}
val positions : Map<String, List<String>> = mapOf(
"above" to listOf("ab", "bc", "de", "ef"),
"above right" to listOf("ae", "bf", "dh", "ei"),
"above left" to listOf("bd", "ce", "eg", "fh"),
"below" to listOf("gh", "hi"),
"left" to listOf("ad", "dg"),
"right" to listOf("cf", "fi"))
for ((pos, nodes) in positions) {
for (node in nodes) {
this["${node[0]}/${node[1]}"] = ";$pos"
this["${node[1]}/${node[0]}"] = ";$pos"
}
}
}
/** Pour graphe normal ou potentiel-tâches avec alpha = 'q' et omega = 'w' */
val q_abc_def_ghi_w_coordinates : (String) -> Pair<Int, Int> = { c ->
when (c) {
"q" -> Pair(-1,-1)
"w" -> Pair(3,-1)
else -> {
val rank = c[0] - 'a'
Pair(rank % 3, -rank / 3)
}
}
}
/** Pour graphe avec 7 sommets */
val abc_defg_coordinates : (String) -> Pair<Int, Int> = { c ->
when (c) {
"g" -> Pair(3,-1)
else -> {
val rank = c[0] - 'a'
Pair(rank % 3, -rank / 3)
}
}
}
}
\ No newline at end of file
......@@ -19,10 +19,7 @@
package fr.ulille.grapp.exercises
import fr.ulille.grapp.exercises.formatting.ClozeFormatter
import fr.ulille.grapp.exercises.formatting.GraphOnGridTikzFormatter
import fr.ulille.grapp.graph.*
import fr.ulille.grapp.io.DotIo
import fr.ulille.grapp.io.Util
import fr.ulille.grapp.ui.AlgorithmsUtil
import fr.ulille.grapp.ui.MaximumFlowAlgorithmName
......
......@@ -23,8 +23,6 @@ package fr.ulille.grapp.exercises
import fr.ulille.grapp.algorithms.Bellman
import fr.ulille.grapp.algorithms.OptimalityCriterion
import fr.ulille.grapp.algorithms.PathCombinationOperation
import fr.ulille.grapp.exercises.formatting.ClozeFormatter
import fr.ulille.grapp.exercises.gen.PertProblemGenerator
import fr.ulille.grapp.graph.*
import fr.ulille.grapp.io.DotIo
import fr.ulille.grapp.io.YamlParser
......@@ -36,6 +34,7 @@ import fr.ulille.grapp.ui.OptimalPathResult
import org.jgrapht.alg.shortestpath.AllDirectedPaths
import org.jgrapht.alg.shortestpath.BFSShortestPath
import java.io.File
import java.lang.IllegalArgumentException
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.extension
......@@ -339,3 +338,121 @@ class PertExerciseGrading (
}
}
object PertProblemGenerator {
private val nbDependencies = 8
const val nbTasks = 7
private val minDuration = 5
private val maxDuration = 25
/** Generates a PERT problem instance with the given tasks and the given number of dependencies between tasks.
* @param tasks must be a set, i.e. w/o repetitions. */
fun generate (tasks: List<String>) : PertProblem {
if (tasks.toSet().size != nbTasks)
throw IllegalArgumentException("Le nombre de tâches doit être égal à 7.")
val pdescr = problem(setOf(1,2,3).random())
val X = tasks.subList(0, pdescr.cardX)
val Y = tasks.subList(pdescr.cardX, pdescr.cardX + pdescr.cardY)
val Z = tasks.subList(pdescr.cardX + pdescr.cardY, nbTasks)
val XwoSucc = X.toMutableSet()
val YwoPred = Y.toMutableSet()
val YwoSucc = Y.toMutableSet()
val ZwoPred = Z.toMutableSet()
val durations = (1..nbDependencies).map{ Random.nextInt(minDuration, maxDuration)}.toMutableList()
val result = mutableMapOf<String,TaskInfo>()
X.forEach { result[it] = TaskInfo(durations.removeFirst(), emptyList())}
val addPairToResult : (Pair<String, String>) -> Unit = { (pred, succ) ->
val ti = result.getOrPut(succ) { TaskInfo(durations.removeFirst(), mutableListOf())}
(ti.predecessors as MutableList).add(pred)
}
randomPairs(pdescr.xz, XwoSucc, X, ZwoPred, Z).forEach { addPairToResult(it) }
randomPairs(pdescr.xy, XwoSucc, X, YwoPred, Y).forEach { addPairToResult(it) }
randomPairs(pdescr.yz, YwoSucc, Y, ZwoPred, Z).forEach { addPairToResult(it) }
randomPairs(pdescr.yy, YwoSucc, Y, YwoPred, Y).forEach { addPairToResult(it) }
return result.entries.sortedBy { it.key }.associate { Pair(it.key, it.value) }
}
/** Selects a random element in 'from' and removes it from 'from'.
* If from is empty, then selects a random element in otherwise. */
private fun randomElement (from: MutableSet<String>, otherwise: Collection<String>) : String {
return if (from.isNotEmpty()) {
val r = from.random()
from.remove(r)
r
} else
return otherwise.random()
}
private fun randomPairs (nbPairs : Int,
first: MutableSet<String>, firstOtherwise: Collection<String>,
second: MutableSet<String>, secondOtherwise: Collection<String>) : Set<Pair<String, String>> {
val result = mutableSetOf<Pair<String,String>>()
var nb = 0
while (nb < nbPairs) {
val f = randomElement(first, firstOtherwise)
val s = randomElement(second, secondOtherwise)
val p = Pair(f,s)
if (! result.contains(p)) {
result.add(p)
nb ++
}
}
return result
}
data class ProblemTypeDescription (
val cardX: Int, val cardY: Int, val cardZ: Int,
val xy : Int, val yz : Int, val xz : Int, val yy: Int
)
/** Three different sets of parameters that influence the shape of the problem.
* Well adapted for nbTasks = 7 and nbDependencies = 8
* How it works:
* - nbTasks tasks
* - nbDependencies total number dependencies between tasks
* - 3 subsets of tasks : X (w/o predecessor), Y (with predecessor and with successor), Z (w/o successor)
* The number of vertices from X to Y is denoted xy, similarly yz, xz and yy
* Type 1:
* - |X| = 2, |Z| = 2, |Y| = nbTasks-|X|-|Y|
* - xz = 1
* - xy = 3
* - yy = 1
* - yz = nbDependencies -xz-xy-yy
* Type 2:
* - |X| = 2, |Z| = 3, |Y| = nbTasks-|X|-|Y|
* - xz = 1 or 2
* - xy = 3
* - yy = 0
* - yz = nbDependencies - xz - xy - yy (so 3 or 4)
* Type 3:
* - symmetric to Type 2 w.r.t. X and Z
*/
private fun problem (type: Int) : ProblemTypeDescription {
return when (type) {
1 -> {
ProblemTypeDescription(
cardX=2, cardY=nbTasks-2-2, cardZ=2,
xy=3, xz=1, yz=nbDependencies-3-1-1, yy=1)
}
2 -> {
val xz = setOf(1,2).random()
ProblemTypeDescription(
cardX=2, cardY=nbTasks-2-3, cardZ=3,
xy=3, xz=xz, yz=nbDependencies-3-xz-0, yy=0)
}
3 -> {
val xz = setOf(1,2).random()
ProblemTypeDescription(
cardX=3, cardY=nbTasks-3-2, cardZ=2,
xz=xz, xy=nbDependencies-xz-3-0, yz=3, yy=0)
}
else ->
throw IllegalArgumentException("Le type de problème peut être 1, 2 ou 3.")
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2022 Iovka Boneva, Université de Lille
*
* This file is part of Grapp.
*
* Grapp is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Grapp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Grapp. If not, see <https://www.gnu.org/licenses/>
*/
package fr.ulille.grapp.exercises.formatting
import fr.ulille.grapp.graph.Edge
import fr.ulille.grapp.graph.Graph
import fr.ulille.grapp.io.LatexSerializer
object GraphOnGridTikzFormatter {
fun standaloneTikzPicture (graph: Graph,
drawInstructions : Map<String, String>,
edgeLabel: (Edge) -> String = LatexSerializer.defaultEdgeLabel(graph.isWeighted())) : String {
val s = StringBuilder()
s.append(
"""
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{arrows,automata,chains,matrix,positioning,scopes,arrows.meta}
\begin{document}
"""
)
graph.setDrawInstructions(drawInstructions)
LatexSerializer.serializeToTikz(graph, s, false, edgeLabel)
s.appendLine("\\end{document}")
return s.toString()
}
/** Pour graphe normal ou réseau de transport avec source 'o' et puits 'p' */
val o_abc_def_ghi_p_drawInstructions = mutableMapOf<String,String>().apply {
this["o"] = "-1,-1"
this["p"] = "3,-1"
for (c in 'a' .. 'i') {
val rank = c - 'a'
this["$c"] = "${rank % 3},${-rank / 3}"
}
val positions : Map<String, List<String>> = mapOf(
"above" to listOf("ab", "bc", "de", "ef"),
"above right" to listOf("ae", "bf", "dh", "ei"),
"above left" to listOf("bd", "ce", "eg", "fh"),
"below" to listOf("gh", "hi"),
"left" to listOf("ad", "dg"),
"right" to listOf("cf", "fi"))
for ((pos, nodes) in positions) {
for (node in nodes) {
this["${node[0]}/${node[1]}"] = ";$pos"
this["${node[1]}/${node[0]}"] = ";$pos"
}
}
}
/** Pour graphe normal ou potentiel-tâches avec alpha = 'q' et omega = 'w' */
val q_abc_def_ghi_w_coordinates : (String) -> Pair<Int, Int> = { c ->
when (c) {
"q" -> Pair(-1,-1)
"w" -> Pair(3,-1)
else -> {
val rank = c[0] - 'a'
Pair(rank % 3, -rank / 3)
}
}
}
/** Pour graphe avec 7 sommets */
val abc_defg_coordinates : (String) -> Pair<Int, Int> = { c ->
when (c) {
"g" -> Pair(3,-1)
else -> {
val rank = c[0] - 'a'
Pair(rank % 3, -rank / 3)
}
}
}
}
\ No newline at end of file
<
/*
* Copyright (c) 2022 Iovka Boneva, Université de Lille
*
* This file is part of Grapp.
*
* Grapp is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Grapp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Grapp. If not, see <https://www.gnu.org/licenses/>
*/
package fr.ulille.grapp.exercises.gen
import fr.ulille.grapp.graph.Graph
import fr.ulille.grapp.graph.Graphs
import fr.ulille.grapp.graph.GrappType
import fr.ulille.grapp.graph.Vertex
import java.lang.IllegalArgumentException
typealias NodeCoord = Pair<Int,Int>
typealias EdgeCoord = Pair<NodeCoord, NodeCoord>
fun NodeCoord.x() = first
fun NodeCoord.y() = second
fun EdgeCoord.src() = first
fun EdgeCoord.tgt() = second
/** Allows to generate a graph which nodes are letters "a", "b", ... and are arranged on a grid. */
class GridGraphGenerator (val nbNodeLines : Int = 3,
val nbNodeColumns : Int = 3,
excluedNodes : Set<Vertex> = setOf()) {
private val xrange = IntRange(0, nbNodeLines-1)
private val yrange = IntRange(0, nbNodeColumns-1)
private val nodesSet = CharRange('a', 'a' + (nbNodeLines * nbNodeColumns - 1))
.map { it.toString() }
.minus(excluedNodes)
private fun toNode(nodeCoord: NodeCoord): String {
return "${('a'.code + (nodeCoord.x() * nbNodeColumns + nodeCoord.y())).toChar()}"
}
private fun toCoordinates (node: Vertex) : NodeCoord {
val nodeRank = (node[0] - 'a')
val x = nodeRank / nbNodeColumns
val y = nodeRank % nbNodeColumns
return Pair(x,y)
}
private fun outgoingEdges (node: NodeCoord) : List<EdgeCoord> {
val result = mutableListOf<EdgeCoord>()
val (x,y) = node
for (i in -1 .. 1)