Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom backend + UI changes #2

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Created by pytest automatically.
*
42 changes: 25 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,35 @@ Clinguin UI for Study regulations

### Usage

#### cogsys.lp:
#### Code from ASPOCP_23:
```
clinguin client-server --domain-files new/instance/cogsys.lp new/encoding.lp --ui-files new/ui.lp -c n=4
clinguin client-server --domain-files instances/instance_ASPOCP_23.lp encodings/encoding_ASPOCP_23.lp --ui-files encodings/ui.lp --custom-classes custom_backend.py --backend CustomBackend -c n=6
```
#### cscbsc.lp:
```
clinguin client-server --domain-files new/instance/cscbsc.lp new/encoding.lp --ui-files new/ui.lp -c n=4
```
#### cscmsc.lp:
```
clinguin client-server --domain-files new/instance/cscmsc.lp new/encoding.lp --ui-files new/ui.lp -c n=4
```
#### dsmsc.lp:
```
clinguin client-server --domain-files new/instance/dsmsc.lp new/encoding.lp --ui-files new/ui.lp -c n=4
```
#### irsba.lp:


## Custom Backend
### update_constant(name, value)

> `update_constant(name, value)` allows users
to set a new value for the constant (name) directly from the UI

To ensure the constant is properly updated in the UI, for declaring a dynamic constant use:
```
clinguin client-server --domain-files new/instance/irsba.lp new/encoding.lp --ui-files new/ui.lp -c n=4
current_constant(I) :- n(N), I = 1..N.
```

### custom_download(file_name)

This function is specific to study regulation formatting. It allows users to download a study plan file with a specified name.


### Clinguin version
`Clinguin 1.0.16`
`Clinguin 1.0.16`



![](out.png)

![](out2.png)

![](out3.png)
7 changes: 7 additions & 0 deletions TODOS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ToDo List
- [ ] create a `set_constant()` in a `custom_backend` that would allow user to update the constant dynamically from the ui (for example to set the number of semesters)
- [x] all tests passed for the custom_backend
- [x] does set_constant work in the ui?
- [ ] ensure that the input is only integer

# Ideas
127 changes: 127 additions & 0 deletions custom_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from collections import defaultdict
import re
from clinguin.server.application.backends import ClingoMultishotBackend
from clingo import Control
from typing import Tuple, Any


class CustomBackend(ClingoMultishotBackend):
def __init__(self, args: Any):
# Parse and store constants
self.current_constants = {
name: value
for const in (args.const or [])
for name, value in [const.split("=")]
}
super().__init__(args)
# Add a method to generate constant facts for the domain state
self._add_domain_state_constructor("_ds_generate_constant_facts")

# ---------------------------------------------
# Setup
# ---------------------------------------------


def _init_ctl(self):
"""Initialize the Clingo control object with current constants and domain files."""
try:
# Create constant definitions and initialize Control
const_defs = [
f"-c {name}={value}" for name, value in self.current_constants.items()
]
self._ctl = Control(["0"] + const_defs)

# Load domain files and add atoms
for f in self._domain_files:
self._ctl.load(str(f))
for atom in self._atoms:
self._ctl.add("base", [], str(atom) + ".")

# Ground the program
self._ctl.ground([("base", [])])
except Exception as e:
self._logger.error(f"Error initializing clingo.Control: {e}")
raise

# ---------------------------------------------
# Public methods
# ---------------------------------------------


def update_constant(self, name: str, value: Any) -> Tuple[bool, str]:
"""Update a constant value and reinitialize the control if necessary."""
try:
name = name.strip('"')
new_value = str(value).strip('"')

if self.current_constants.get(name) != new_value:
self.current_constants[name] = new_value
self._assumptions = set()
self._outdate()
self._init_ctl()
self._ground()
return True, f"Constant {name} updated successfully to {new_value}"
except ValueError:
return (
False,
f"Error updating constant {name}: Provided value must be an integer",
)
except Exception as e:
return False, f"Error updating constant {name}: {str(e)}"


def custom_download(self, file_name="study_plan.lp"):
"""
Custom download function to save the solution in a more user-friendly format,
showing only assignments related to semesters and formatting them.
"""
if self._model is None:
raise RuntimeError("Can't download when there is no model")

# Filter the model to include only semester assignments
semester_assignments = [
f"{str(atom)}."
for atom in self._model
if re.match(r"in\(\w+,s\(\d+\)\)", str(atom))
]

# Format the StudyPlan
semesters = defaultdict(list)
for line in semester_assignments:
match = re.match(r"in\((\w+),s\((\d+)\)\)\.", line.strip())
if match:
module, semester = match.groups()
semesters[int(semester)].append(module)

formatted_output = ""
for semester in sorted(semesters.keys()):
formatted_output += f"Semester {semester}:\n"
for module in semesters[semester]:
formatted_output += f"{module}\n"
formatted_output += "\n"

# Write the formatted output to the file
file_name = file_name.strip('"')
with open(file_name, "w", encoding="UTF-8") as file:
file.write(formatted_output)

self._messages.append(
(
"Download successful",
f"Information saved in file {file_name}.",
"success",
)
)

# ---------------------------------------------
# Properties
# ---------------------------------------------

@property
def _ds_generate_constant_facts(self) -> str:
"""Generate facts for current constants."""
facts = [
f"_clinguin_const({name},{value})."
for name, value in self.current_constants.items()
]
return "#defined _clinguin_const/2." + " ".join(facts) + "\n"
File renamed without changes.
192 changes: 192 additions & 0 deletions encodings/ui.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Semester Definition
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

semester(I) :- _clinguin_const(n,N), I = 1..N.
values(3..8).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Root Window Configuration
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(w, window, root).
attr(w, flex_direction, column).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Container for 'n' value display and update button
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(n_input_container, container, w).
attr(n_input_container, order, 4).
attr(n_input_container, flex_direction, column).
attr(n_input_container, class, ("m-2")).
attr(n_input_container, class, "align-self-center").

elem(n_l, label, n_input_container).
attr(n_l, label, @concat("Current number of semesters: ", N)) :- _clinguin_const(n, N).
attr(n_l, order, 1).
attr(n_l, class, ("fw-bold"; "p-2"; "m-2")).

elem(n_dd, dropdown_menu, n_input_container).
attr(n_dd, order, 2).
attr(n_dd, selected, "Change").
attr(n_dd, class, ("btn-sm"; "btn-secondary"; "p-2"; "m-2")).

elem(n_ddi(V), dropdown_menu_item, n_dd) :- values(V).
attr(n_ddi(V), label, V) :- values(V).
when(n_ddi(V), click, call, update_constant("n", V)) :- values(V).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Container for semesters
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(semester_container, container, w).
attr(semester_container, order, 2).
attr(semester_container, flex_direction, row).
attr(semester_container, class, "align-self-center").

elem(s(I), container, semester_container) :- semester(I).
attr(s(I), order, I) :- semester(I).
attr(s(I), class, ("border-opacity-50";"fw-bold";"p-3";"m-2"; "rounded"; "border"; "border-secondary"; "border-2")):-semester(I).

elem(s_t(I), container, s(I)):-semester(I).
attr(s_t(I), order, 1):-semester(I).
attr(s_t(I), class, ("bg-secondary";"text-light";"fw-bold";"p-2";"m-1"; "rounded")):-semester(I).

elem(s_l(I), label, s_t(I)):-semester(I).
attr(s_l(I), label, @concat("Semester ",I)):-semester(I).
attr(s_l(I), order, 1):-semester(I).

elem(s_dd(I), dropdown_menu, s_t(I)):-semester(I).
attr(s_dd(I), order, 2):-semester(I).
attr(s_dd(I), selected, "Assign module"):-semester(I).
attr(s_dd(I), class, ("btn-sm";"btn-outline-light"; "m-2")):-semester(I).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Updated dropdown menu item to include both module name and
% credits
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(s_ddi(I,E), dropdown_menu_item, s_dd(I)):-_any(in(E,s(I))), not _all(in(E,s(I))).
attr(s_ddi(I,E), label, @concat(E, " (", C, " ECTS)")):-_any(in(E,s(I))), not _all(in(E,s(I))), map(c, E, C).
when(s_ddi(I,E), click, call, add_assumption(in(E,s(I)))):-_any(in(E,s(I))), not _all(in(E,s(I))).

elem(s_modules(I), container, s(I)):-semester(I).
attr(s_modules(I), class, ("bg-white")):-semester(I).
attr(s_modules(I), order, 2):-semester(I).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Module display in semesters
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

shown_module(E,I):-_all(in(E,s(I))).
shown_module(E,I):-in(E,s(I)),_clinguin_browsing.

elem(s_module(I,E), container, s_modules(I)):-shown_module(E,I).
attr(s_module(I,E), height, C*10):-shown_module(E,I), map(c,E,C).
attr(s_module(I,E), class, ("border"; "border-secondary";
"d-flex";"flex-row";"justify-content-between";"align-items-center";
"p-3";"m-2"; "rounded"; "border-2"; "border-opacity-50")):-shown_module(E,I).

elem(s_module_l(I,E), label, s_module(I,E)):-shown_module(E,I).
attr(s_module_l(I,E), label, E):-shown_module(E,I).
attr(s_module_l(I,E), class, ("text-black"; "fw-bold")):-shown_module(E,I).

elem(s_module_b(I,E), button, s_module(I,E)):-_clinguin_assume(in(E,s(I))).
attr(s_module_b(I,E), icon, "fa-times"):-_clinguin_assume(in(E,s(I))).
attr(s_module_b(I,E), class, ("btn-sm";"btn-outline")):-_clinguin_assume(in(E,s(I))).
when(s_module_b(I,E), click, call, remove_assumption(in(E,s(I)))):-_clinguin_assume(in(E,s(I))).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Main Menu Bar Configuration
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(menu_bar, menu_bar, w).
attr(menu_bar, title, "Study Regulations").
attr(menu_bar, icon, "fa-graduation-cap").

elem(menu_bar_clear, button, menu_bar).
attr(menu_bar_clear, label, "Clear").
attr(menu_bar_clear, icon, "fa-trash").
attr(menu_bar_clear, class, ("btn-sm";"btn-outline-secondary")).
when(menu_bar_clear, click, call, clear_assumptions).

elem(menu_bar_next_opt, button, menu_bar).
attr(menu_bar_next_opt, label, "Next Optimal").
attr(menu_bar_next_opt, icon, "fa-forward-fast").
attr(menu_bar_next_opt, class, ("btn-sm";"btn-outline-secondary")).
when(menu_bar_next_opt, click, callback, next_solution(optN)).

elem(menu_bar_next, button, menu_bar).
attr(menu_bar_next, label, "Next").
attr(menu_bar_next, icon, "fa-forward-step").
attr(menu_bar_next, class, ("btn-sm";"btn-outline-secondary")).
when(menu_bar_next, click, call, next_solution).

elem(menu_bar_select, button, menu_bar).
attr(menu_bar_select, label, "Select").
attr(menu_bar_select, icon, "fa-hand-pointer").
attr(menu_bar_select, class, ("btn-sm";"btn-outline-secondary")).
when(menu_bar_select, click, call, select).

elem(menu_bar_download, button, menu_bar).
attr(menu_bar_download, label, "Download Study Plan").
attr(menu_bar_download, icon, "fa-download").
attr(menu_bar_download, class, ("btn-sm";"btn-outline-secondary")).
when(menu_bar_download, click, update, (download_modal, visible, shown)).

elem(menu_bar_download_asp, button, menu_bar).
attr(menu_bar_download_asp, label, "Download ASP Model").
attr(menu_bar_download_asp, icon, "fa-download").
attr(menu_bar_download_asp, class, ("btn-sm";"btn-outline-secondary")).
when(menu_bar_download_asp, click, call, download("#show in/2.","asp_study_plan.lp")).

elem(download_modal, modal, w).
attr(download_modal, title, "Download Study Plan").

elem(download_filename, textfield, download_modal).
attr(download_filename, placeholder, "Enter file name (e.g., study_plan.txt)").
attr(download_filename, width, 300).
when(download_filename, input, context, (file_name, _value)).

elem(download_button, button, download_modal).
attr(download_button, label, "Download").
attr(download_button, class, ("btn-primary", "mt-3")).
when(download_button, click, call, custom_download(_context_value(file_name, str, "semester_assignments.txt"))).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Module Information Modal
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(module_info_modal(E), modal, w) :- in(E,_).
attr(module_info_modal(E), title, E) :- in(E,_).

elem(module_info_credits(E), label, module_info_modal(E)) :- in(E,_).
attr(module_info_credits(E), label, @concat("Credits: ", C)) :- map(c,E,C).

when(s_module_l(I,E), click, update, (module_info_modal(E), visible, shown)) :- shown_module(E,I).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Credits Display Under Each Semester
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(s_credits(I), container, s(I)) :- semester(I).
attr(s_credits(I), order, 3) :- semester(I).
attr(s_credits(I), class, ("bg-light"; "p-2"; "mt-2"; "rounded"; "text-center"; "fw-bold")) :- semester(I).

elem(s_credits_label(I), label, s_credits(I)) :- semester(I).
attr(s_credits_label(I), label, @concat(SC, " ECTS")) :-
semester(I),
SC = #sum{ C,E : map(c,E,C), shown_module(E,I) }.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Total Credits Display at the Top of the Page
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

elem(total_credits_container, container, w).
attr(total_credits_container, order, 1).
attr(total_credits_container, class, ("bg-secondary"; "text-white"; "p-3"; "rounded"; "m-2"; "align-self-center")).

elem(total_credits_label, label, total_credits_container).
attr(total_credits_label, label, @concat("Total credits: ", TC)) :-
TC = #sum{ C,E : map(c,E,C), shown_module(E,_) }.
Loading