Does Optaplanner support "combinatorial" planning variables? - optaplanner

Example:
Students want to enroll in multiple courses of different course groups (Math, English, Spanish, History) and have issued preferences for each course group (ENG-1 > ENG-2 means course ENG-1 is preferred to course ENG-2).
Student A:
MATH-2 > MATH-4 > MATH-1 > ... > MATH-9
ENG-3 > ENG-4 > ENG-1 > ... > ENG-2
Student B:
ENG-1 > ENG-2 > ENG-4 > ... > ENG-3
SPA-4 > SPA-6 > SPA-3 > ... > SPA-2
HIST-1 > HIST-3 > HIST-2 ... > HIST-5
Student C:
...
Is it possible for a planning variable of each student (planning entity) to be a combination of each of their preferences? I.e. student A would be assigned MATH-2 and ENG-3, student B would be assigned ENG-1, SPA-4, and HIST-1, if the constraints allow for this.

Yes (and no). Technically no, because #PlanningVariable can only hold a single value.
But YES, OptaPlanner can handle your use case. You just need to choose the right way to map your domain to Java classes. You need to model a N:M relation between Student and Course:
Student A needs to enroll in 2 courses (one from the MATH group and one from the ENG group).
Student B needs to enroll in 3 courses (ENG, SPA, HIST).
etc.
You can model this type of relationship with the CourseAssignment class, which is your #PlanningEntity. It could look like this:
#PlanningEntity
class CourseAssignment {
final Student student; // e.g. Ann
final CourseGroup courseGroup; // e.g. MATH
#PlanningVariable(valueRangeProviderRefs = { "courseRange" })
Course course; // changed by Solver - could be MATH-1, MATH-2, ENG-1, HIST-...
}
Since the number of course assignments is known for each student and it's fixed, you'd simply create 2 CourseAssignment instances for Student A, 3 instances for Student B, etc.
Next, design your constraints to penalize each courseAssignment with a hard score penalty, if courseAssignment.course.group != courseAssignment.courseGroup and with a soft score penalty based on courseAssignment.student.getPreference(courseAssignment.course).

Related

Translate "there exists one" from English sentence to ALCQO description logic

I have the following English question
There exists one school student.
And I would like to translate it to ALCQO or First order logic.
I have written the following:
∃SchoolStudent
or in First Order Logic:
∃x(SchoolStudent(x))
However, I know from mathematics and the theory that ∃ is translated as "at least one" or "some". Thus, I am wondering if the following two options are correct:
approach 1
¬∃x(SchoolStudent(x))
approach 2
(≥1student.SchoolStudent)⊓(≤1student.SchoolStudent)
∃SchoolStudent is meaningless in ALCQO. The only constructor available in ALCQO using existential restrictions are of the form
∃r.C
which is interpreted as
{d ∈ ΔI | there is an e ∈ ΔI with (d, e) ∈ rI and e ∈ CI}
In first order logic ∃r.C translates to πx(∃r.C) = ∃y.r(x, y) ∧ πy(C).
To define a school with only 1 student you will have to have the concepts School and Student and a role which can be used to associate a student with a school, say hasStudent.
School ≡ ∃hasStudent.Student ⊓ ≤1hasStudent.Student
This means School here is the set of schools that have only a single student.
But we can also define School as anything that has more than 1 student and then define a specific individual of the school as a school with only 1 student.
School ≡ ∃hasStudent.Student
School(x)
≤1hasStudent.Student(x)

ActiveRecord: Exclude group if at least one record within it doesn't meet condition

I have two models: an owner and a pet. An owner has_many :pets and a pet belongs_to :owner.
What I want to do is grab only those owners that have pets which ALL weigh over 30lbs.
#app/models/owner.rb
class Owner < ActiveRecord::Base
has_many :pets
#return only those owners that have heavy pets
end
#app/models/pet.rb
class Pet < ActiveRecord::Base
belongs_to :owner
scope :heavy, ->{ where(["weight > ?", 30])}
end
Here is what is in my database. I have three owners:
Neil, and ALL of which ARE heavy;
John, and ALL of which ARE NOT heavy;
Bob, and SOME of his pets ARE heavy and SOME that ARE NOT heavy.
The query should return only Neil. Right now my attempts return Neil and Bob.
You can form a group for each owner_id and check, if all rows within group match required condition or at least one row doesn't match it, you can achieve it with group by and having clauses:
scope :heavy, -> { group("owner_id").having(["count(case when weight <= ? then weight end) = 0", 30]) }
There is also another option, more of a Rails-ActiverRecord approach:
scope :heavy, -> { where.not(owner_id: Pet.where(["weight <= ?", 30]).distinct.pluck(:owner_id)).distinct }
Here you get all owner_ids that don't fit condition (searching by contradiction) and exclude them from the result of original query.
Isn't this simply a matter of finding the owners for whom the minimum pet weight is greater than some value:
scope :heavy, -> { group("owner_id").joins(:pets).having("min(pets.weight) >= ?", 30)}
Or conversely,
scope :light, -> { group("owner_id").joins(:pets).having("max(pets.weight) < ?", 30)}
These are scopes on the Owner, by the way, not the Pet
Another approach is to turn this into a scope on Owner:
Owner.where(Pet.where.not("pets.owner_id = owners.id and pets.weight < ?", 30).exists)
Subtly different, as it is checking for the non-existence of a per with a weight less than 30, so if an owner has no pets then this condition will match for that owner.
In database terms, this is going to be the most efficient query for large data sets.
Indexing of pets(owner_id, weight) is recommended for both these approaches.
What if you do it in two steps, first you get all owner_ids that have at least 1 heavy pet, then get all owner_ids that have at least 1 not-heavy pet and then grab the owners where id exists in the first array but not in the second?
Something like:
scope :not_heavy, -> { where('weight <= ?', 30) }
...
owner_ids = Pet.heavy.pluck(:owner_id) - Pet.not_heavy.pluck(:owner_id)
owners_with_all_pets_heavy = Owner.where(id: owner_ids)
You can just add a the uniq to your scope:
scope :heavy_pets, -> { uniq.joins(:pets).merge(Pet.heavy) }
It works on a database level, using the distinct query.

Subqueries for each row

Using these tables:
*student {'s_id','s_name,'...} , class {'c_id','c_name',...} student2class {'s_id','c_id'}, grades {'s_id','c_id','grade'}*
Is it possible to perform a query (nested query?) put class name as subtitle and then all students (of that class) and grades, next class name as subtitle ...
The result I need is:
Maths
John .... C
Anna .... B
[...]
Biology
Anna .... C
Jack .... A
[...]
For each row from class I'll have a subquery fetching all data related with this class
No need of any sub-query. You can get your data this way:
SELECT c_name, s_name_, grade
FROM student, class, grades
WHERE student.s_id = grades.s_id and class.c_id = grades.c_id
ORDER BY c_name
The presentation of results depends on your system/tools, as others already said. This is a link to the solution for Microsoft Access:
http://office.microsoft.com/en-001/access-help/create-a-grouped-or-summary-report-HA001159160.aspx
The solution should be implemented in your client side code and not in the database. From database you should just get a simple table formatted data (subject, student, grade)
Then convert the above recordset to the format you want:
For an example in C# you could convert the recordset into lookup
var Lookup = DataSet.Tables[0].Rows.ToLookup(x => x["subjectColumn"]);
now you can loop through the lookup and format your result
foreach (var grade in Lookup)
{
subject = grade.Key;
...
}

Certain size from collection, including second filter with HQL?

I'm struggling with following scenario: Let's assume I have the two entities Classroom and Member, mapped with many-to-many. Classroom has the collection Members, containing the entities Member.
I would like to get the classrooms which do have members of a certain count. That would result in something like:
FROM Classroom cr WHERE cr.Members.size < 10
Now I have a Type on Classroom. I'd like to filter first on Type, then the size. This won't work:
FROM Classroom cr WHERE cr.Members.size < 10 AND cr.Members.Type = 1
Results in: illegal attempt to dereference collection
How could I write such a query?
I would imagine you need to do a join
from Classroom as cr left join cr.Members as m
where cr.Members.size < 10 and m.Type = 1

nHibernate collections and alias criteria

I have a simple test object model in which there are schools, and a school has a collection of students.
I would like to retrieve a school and all its students who are above a certain age.
I carry out the following query, which obtains a given school and the children which are above a certain age:
public School GetSchoolAndStudentsWithDOBAbove(int schoolid, DateTime dob)
{
var school = this.Session.CreateCriteria(typeof(School))
.CreateAlias("Students", "students")
.Add(Expression.And(Expression.Eq("SchoolId", schoolid), Expression.Gt("students.DOB", dob)))
.UniqueResult<School>();
return school;
}
This all works fine and I can see the query going to the database and returning the expected number of rows.
However, when I carry out either of the following, it gives me the total number of students in the given school (regardless of the preceding request) by running another query:
foreach (Student st in s.Students)
{
Console.WriteLine(st.FirstName);
}
Assert.AreEqual(s.Students.Count, 3);
Can anyone explain why?
You made your query on the School class and you restricted your results on it, not on the mapped related objects.
Now there are many ways to do this.
You can make a static filter as IanL said, however its not really flexible.
You can just iterate the collection like mxmissile but that is ugly and slow (especially considering lazy loading considerations)
I would provide 2 different solutions:
In the first you maintain the query you have and you fire a dynamic filter on the collection (maintaining a lazy-loaded collection) and doing a round-trip to the database:
var school = GetSchoolAndStudentsWithDOBAbove(5, dob);
IQuery qDob = nhSession.CreateFilter(school.Students, "where DOB > :dob").SetDateTime("dob", dob);
IList<Student> dobedSchoolStudents = qDob.List<Student>();
In the second solution just fetch both the school and the students in one shot:
object result = nhSession.CreateQuery(
"select ss, st from School ss, Student st
where ss.Id = st.School.Id and ss.Id = :schId and st.DOB > :dob")
.SetInt32("schId", 5).SetDateTime("dob", dob).List();
ss is a School object and st is a Student collection.
And this can definitely be done using the criteria query you use now (using Projections)
Unfortunately s.Students will not contain your "queried" results. You will have to create a separate query for Students to reach your goal.
foreach(var st in s.Students.Where(x => x.DOB > dob))
Console.WriteLine(st.FirstName);
Warning: That will still make second trip to the db depending on your mapping, and it will still retrieve all students.
I'm not sure but you could possibly use Projections to do all this in one query, but I am by no means an expert on that.
You do have the option of filtering data. If it there is a single instance of the query mxmissle option would be the better choice.
Nhibernate Filter Documentation
Filters do have there uses, but depending on the version you are using there can be issues where filtered collections are not cached correctly.