I am cataloging various ways of modeling the farmer, goat, cabbage, wolf problem.
Below are two ways to model the problem. Are there other, reasonable, ways to model it?
One model defines a set of River objects. Each River object represents a snapshot of the River and its two sides after the farmer has done a ferry.
sig River {
side1: set Item,
side2: set Item
}
Another model has one River object. The items on the two sides of the River vary over time.
one sig River {
side1: Item -> Time,
side2: Item -> Time
}
What are other, reasonable, ways to model the farmer, goat, cabbage, wolf problem?
Let's imagine the puzzle as an ordered series of situations where a situation contains location-item pairs.
open util/ordering[Situation]
abstract sig Location {}
one sig SideA, SideB extends Location {}
abstract sig Item {}
one sig Goat, Cabbage, Wolf, Farmer extends Item {}
sig Situation {
l_i: Location -> Item
}
Puzzle rules are easy to form then:
// in the initial situation everyone is on SideA
one s: Situation & first | all i: Item | s.l_i.i = SideA
// in the final situation everyone is on SideB
one s: Situation & last | all i: Item | s.l_i.i = SideB
// in all other situations the locations of the goat/wolf and the cabbage/goat must be diffent, except when the farmer is also there
all s: Situation - first - last | (s.l_i.Goat != s.l_i.Cabbage) or s.l_i.Goat = s.l_i.Farmer
all s: Situation - first - last | (s.l_i.Goat != s.l_i.Wolf) or s.l_i.Goat = s.l_i.Farmer
// further puzzle constraints ...
I'm curious if there's a more compact way to refer to the first item in an ordering than
one s: Situation & first
I want to group people into smaller subgroups, and after shuffling groups multiple times for successive sessions, make all the people meet each other at least once.
In every session, people are divided into a constant number of groups. Everyone has to join one group in every session.
The group size should be closest to (the number of people)/(# of groups). There should not be a groups of too few people or too many people.
The sessions are continued until every pair of people meets each other at least once.
Preferably, the number of times the same pair meet each other should be minimized.
The following is the answer for this problem for 11 people (numbered 0-10) and 3 groups (3 columns). It requires 5 sessions.
Session 1: 3,6,8,10 0,1,7,9 2,4,5
Session 2: 3,5,7,8 0,1,2,10 4,6,9
Session 3: 0,1,6,8 2,3,4,9 5,7,10
Session 4: 0,3,5,9 1,4,8,10 2,6,7
Session 5: 1,3,5,6 2,8,9,10 0,4,7
Members of two groups of different size must meet each other (1v1, once)
The question above is similar, but I want rather make the people meet just in a group of a larger group, not 1-on-1.
The following is my approach that uses Alloy. This works for a small number of people (~15) and groups (~2), but it quickly causes computation time explosion when the size is increased. I need to calculate it for ~25 people and ~5 groups.
module Teaming
sig Person { groups: some Group }
sig Group { people: some Person }
sig Session { groups: some Group }
one sig Sessions { sessions: some Session }
sig GroupPerSession {}
-- Tree structures
fact {
all s: Session | s in Sessions.sessions
all g: Group | g in Session.groups
all s: Session | all p:Person | p in s.groups.people
people =~ groups
}
-- The total number of people
fact {
all s: Session | #s.groups.people = #Person
}
-- The number of groups per session
fact {
all s: Session | #s.groups = #GroupPerSession
}
-- The number of people in a group
fact {
all g: Group | (#g.people) >= div[#(Person), #(GroupPerSession)] and (#g.people) <= add[div[#Person,#GroupPerSession],1]
}
-- Mutually exclusive grouping in a session
fact separate {
all s: Session | all disj a,b: s.groups | no p: Person | p in a.people and p in b.people
}
-- Every pair of people meets somewhere
pred sameGroup {
all disj a,b: Person | some g: Group | a in g.people and b in g.people
}
-- The same people should not meet too many times
fact sameGroupNotTooMuch {
all disj a,b: Person | #{a.groups & b.groups} <= 3
}
run sameGroup for 6 Int, 5 Session, 15 Group, exactly 3 GroupPerSession, exactly 16 Person
run sameGroup for 6 Int, 6 Session, 24 Group, exactly 4 GroupPerSession, exactly 18 Person
run sameGroup for 6 Int, 7 Session, 35 Group, exactly 5 GroupPerSession, exactly 18 Person
I guess dynamic programming should work, though I cannot find anything specific. Any pointer for the improvement in Alloy code or other algorithms would be great.
Here's my quick shot at solving this problem.
Overall, the generation of instance seems faster, but still have difficulty to complete when trying to assign >20 people into >4 groups.
module Teaming
one sig Settings{
maxEncounter:Int,
minGroupSize:Int,
maxGroupSize:Int
}{
// Manually filling values there helps (1)reducing the integer bit-width needed (2) decrease the complexity (in terms of clauses)
maxEncounter=4
//minGroupSize=5
//maxGroupSize=5
minGroupSize=div[#Person, #Group]
maxGroupSize=add[minGroupSize,1]
}
sig Session{}{
Group.people[this]=Person // all person are assigned in group during a session
no disj g1,g2 :Group| g1.people[this] & g2.people [this] !=none // a person can't be in two disjoint groups of a same session
}
sig Group {
people: Session some -> some Person
}{
all s:Session| #people[s]<= Settings.maxGroupSize and #people[s]>=Settings.minGroupSize
}
sig Person {}
pred allMeet {
all disj a,b: Person | people. a & people.b != none->none
}
pred allMeetAndMinEncounter {
all disj a,b: Person | let x= (people. a & people.b) {
#x <=Settings.maxEncounter
x != none ->none
}
}
run allMeet for 6 Int, 9 Session, exactly 4 Group, exactly 20 Person
Highlight of the changes brought:
Removed quantifications when possible
Removed redundant constraints
Replaced the two binary relations groups and people by a ternary relation. This comes with several advantages:
It reduces the number of group atoms present in an instance to the total of group per session.
It enables the use of instance projection in the instance viewer. You'll now be able e.g. to view group assignment for each session separately.
I do not think Alloy is the right tool for optimisation. It is a specification tool that focuses on finding counter examples. However, it can of course be found to find solutions to puzzles like this. (I think there is a group that developed an extension to Alloy that minimises the found solutions.)
I took a stab though I am a beginner. Surprisingly I found a solution with 4 sessions 11 people and 3 groups.
s0 {2,9,10}, {5,6,7,8}, {0,1,3,4},
s1 {2,4,7,8}, {1,3,6,10}, {0,5,9},
s2 {1,2,3,5}, {0,7,8,10}, {4,6,9},
s3 {0,2,6}, {4,5,10}, {1,3,7,8,9}
Since Alloy is not an optimiser I used the binary way to find the minimum number of sessions, I found that you needed at least 7 sessions for 25/5.
This is my full model:
sig P, Group {}
sig Session {
participants : Group -> P
}
fact {
// tuning goes here (max sure <= scope )
# Group = 5
# P = 25
# Session = 7
// In every session, people are divided into a constant number
// of groups.
all s : Session | s.participants.P = Group
// The sessions are continued until every pair of people
// meets each other at least once.
// Preferably, the number of times the same pair meet
// each other should be minimized.
all disj a,b : P | some participants.a & participants.b
// Everyone has to join one group in every session.
all p : P, s : Session | one s.participants.p
// The group size should be closest to (the number
// of people)/(# of groups).
// There should not be a
// groups of too few people or too many people.
all g : Group, s : Session | (# s.participants[g]) >= 3
}
run {} for 5 Group, 25 P, 24 Session, 6 int
Specifying the int width for these number is crucial.
Finding a series of 8 sessions took 5 seconds, finding the 7 sessions took much longer, 34 seconds. Forcing a more equal group size by increasing the minimum is still running :-)
I think the tool does exactly what it is supposed to do: finding a solution. It is not that good in finding an optimal solution.
I'm using this simple model and the evaluator to study relation multiplicities. The evaluator dialogue demonstrates a situation that I find puzzling.
sig A {}
sig B {}
pred show {}
run show
Evaluator dialogue:
univ
{A$0, A$1, B$0, B$1, B$2}
B->A
{B$0->A$0, B$0->A$1, B$1->A$0, B$1->A$1, B$2->A$0, B$2->A$1}
B lone -> A
{B$0->A$0, B$0->A$1, B$1->A$0, B$1->A$1, B$2->A$0, B$2->A$1}
// Although the two relations seem to be identical,
B->A in B->A
true
// but
B->A in B lone -> A
false
What makes the two products different in answering 'B->A in B->A' and 'B->A in B lone -> A'?
I am using PlantUML to make simple class diagrams and the tool is awesome, but I couldn't find any way to align classes with each other except putting them into packages or using relationships like Alice -left-* Bob. What I need is something like:
#startuml
class Bob
class Alice
class Dan
**Dan aligned Alice: horizontally**
'or using a grid?
**Bob at grid (2, 3)**
#enduml
Is there a way?
UPDATES Aug.08.2019
From Rotsiser's comment, by combining changing the length of lines with together keyword, it can align elements
#startuml
class A
A ..> B
C ---> B
D ...> B
together {
class E
class F
class G
}
E ----> B
#enduml
OUTDATED
You are able to align elements by changing the number of line's character, such as '-', '.', and so on.
#startuml
class A
A ..> B
C ---> B
D ...> B
E ----> B
F ----> B
G ----> B
#enduml
Using a -[hidden] relation can do the job :
#startuml
class Bob
class Alice
class Dan
class Foo
class Bar
class Foobar
Bob -[hidden] Alice
Bar -[hidden] Foobar
#enduml
No, there's no way to do that, sorry :( The idea behind PlantUML is that you should not care too much about the layout rendering.
Actually, early versions of PlantUML use to align classes, but it was an issue: When there were many unrelated classes, diagrams tended to be very large and very thin. So a patch was added to organize classes in a square.
How many classes do you want to have in your diagram? Sure it would be possible to disable the organizing patch for e.g. 3 to 5 classes. You could post a suggestion to the forum to see what other users think about it.
Here is a solution.
The documentation:
"It is also possible to change arrow direction by adding left, right, up or down keywords inside the arrow:"
#startuml
foo -left-> dummyLeft
foo -right-> dummyRight
foo -up-> dummyUp
foo -down-> dummyDown
#enduml
To your question:
#startuml
class Bob
class Alice
class Dan
Alice -left[hidden]-> Bob
Alice -right[hidden]-> Dan
#enduml
It may also be useful:
#startuml
class Bob
class Alice
class Dan
Bob -right-|> Alice
Alice -right-> Dan
interface Friend
Dan -up..> Friend
interface Person
Friend -left-> Person
interface Object
Person -down-> Object
interface Native
Object -right-> Native
#enduml
You don't need a hidden package, use the together keyword:
together {
class A
class B
}
A cleaner approach is to put them in a hidden package, which is more logical.
#startuml
skinparam shadowing false
skinparam package<<Layout>> {
borderColor Transparent
backgroundColor Transparent
fontColor Transparent
stereotypeFontColor Transparent
}
package x <<Layout>>{
class A
class B
}
A .. D
B .. C
C .. D
A1 .. D1
B1 .. C1
C1 .. D1
#end
Consider a upgrades relationship:
I need to make sure that upgrades cannot be circular. How can I do that in Alloy?
It is sufficient to enforce transitivity and antireflexivity.
fact {
no a: Item | a in a.upgrades
}
fact{
all a,b,c: Item |
a in b.upgrades and b in c.upgrades implies
a in c.upgrades
}
From your example, I infer that the upgrades relation is not intended to be transitive: in the example, a diamond sword upgrades a stone sword, and a stone sword upgrades a wooden sword, but the pair WoodSword -> DiamondSword is not in the upgrades relation.
So what you want to say is something like
fact upgrades_acyclic {
no x : univ | x in x.^upgrades
}
Some modelers prefer the more succinct formulation in terms of relations:
fact upgrades_acyclic { no ^upgrades & iden }