Skip to content

Special transformations for operators => and <=>#372

Merged
maxkratz merged 16 commits intomasterfrom
feature/implication-shortcuts
Apr 30, 2026
Merged

Special transformations for operators => and <=>#372
maxkratz merged 16 commits intomasterfrom
feature/implication-shortcuts

Conversation

@MarkBeB
Copy link
Copy Markdown
Collaborator

@MarkBeB MarkBeB commented Apr 24, 2026

This PR introduces shortcut transformations for => and <=> and should resolve #236 .
To enable these special linearisations, two new operators have been added to GIPSL: ==> and <==>.

Automatic detection would have been preferable, but this isn't possible in many cases because some of the necessary information is only available at runtime (too late!).

If the operator ==> or <==> is used, the GIPSL validator will check its usage against known patterns. If no match is found, an error will be thrown.

The following list contains all of the implemented patterns. Each capital letter (e.g. A) represents an arithmetic expression. Please note that it is not necessary to follow the order of operands exactly as listed. The letter M refers to a 'large' enough number (which can be set via Window -> Preferences -> Gipsl -> Code Generator -> Big M)

  • A >= 1 <==> B == 1 (& C == 1 & ...)
    Resolves to: B (+ C + ...) + (1-n) <= A & A <= B*M (& A <= C*M & ...)

  • A == 1 <-> B == 1 (& C == 1 & ...)
    Resolves to: B (+ C + ...) + (1-n) <= A & A <= B (& A <= C & ...)

  • A == 1 <-> B == 1 | C == 1 (| D == 1 | ...)
    Resolves to: A <= B + C (+ D + ...) & B + C (+ D + ...) <= A*n

  • A == 0 <-> B == 1
    Resolves to: (1-B) <= A & A <= (1-B) * M

  • A == 1 -> B == 1
    Resolves to: A <= B

@maxkratz
Copy link
Copy Markdown
Member

Thanks for your implementation, @MarkBeB!

I replaced the generic operators with the new special ones on one of my feature branches on the virtualized IHTP project and all but one worked flawlessly.

I have two points for you to please consider here.


1. For one constraint, the validator did not let me use the new operator.

The expression was very similar to this:

constraint with mappingA {
  context.variables.v >= 1 // `v` is an integer variable that can be larger than 1
  <==>
  [
    mappings.B->[...]->sum(element.value) == 1
    &
    mappings.C->[...]->sum(element.value) == 1
    &
    mappings.D->[...]->sum(element.value) == 1
  ]
}

If I am not mistaken, the problem is not the operator >= but the type of the variable x.

The constraint can be seen here: https://github.com/maxkratz/dissertation-ihtp/blob/520f4667abb7b0da3f9806c9e6968ebe216bd648/ihtcvirtualgipssolution/src/ihtcvirtualgipssolution/Model.gipsl#L428-L455

I thought this would fall into the first case you described above:

A >= 1 <==> B == 1 (& C == 1 & ...)
Resolves to: B (+ C + ...) + (1-n) <= A & A <= B*M (& A <= C*M & ...)

Can you please have a look at this? :)


2. Addition of another complex case

If we are able to express another complex case that came up in my specification, that would help me to eliminate two additional variables and two additional constraints from the GIPSL spec.

Consider the following constraint:

constraint with mappingA
  context.variables.v == 1 // v is in {0,1}
  <=>
  [
    context.value == 1
    &
    mappings.B->[...]->sum(element.value) == 0 // the sum can have values > 1
  ]
}

If we define a substitute variable s in {0,1} with

  • B := s and ...
  • A := mappings.B->[...]->sum(element.value),
    we can use your replacement of ...

A == 0 <-> B == 1
Resolves to: (1-B) <= A & A <= (1-B) * M

to represent the term mappings.B->[...]->sum(element.value) with s in other replacements.

Now we can use ...

A == 1 <-> B == 1 (& C == 1 & ...)
Resolves to: B (+ C + ...) + (1-n) <= A & A <= B (& A <= C & ...)

in a simplified form:

  • A == 1 <-> B == 1 & C == 1
    • B + C - 1 <= A
    • A <= B
    • A <= C
  • Fill in terms:
    • A := v
    • B := context.value
    • C := s

This would lead to the following complete set of replacement constraints for the new complex case above:

// replacement of original <=> operator
context.value + s - 1 <= v
v <= context.value
v <= s

// s == 1 <=> mapping sum == 0
1 - mappings.B->[...]->sum(element.value) <= s
mappings.B->[...]->sum(element.value) <= (1 - s) * M

Can you please check if this can be included in your new implementation of the shortcut operators @MarkBeB ?

Sorry that it took me so long to come up with this - I did not see this (further) case coming when first replacing the operators in my spec.

@MarkBeB
Copy link
Copy Markdown
Collaborator Author

MarkBeB commented Apr 27, 2026

1. For one constraint, the validator did not let me use the new operator.

The linked constraint doesn't match any case.

It translates to: A >= 1 <-> [ B == 1 & C == 1 & D == 1 ] & E <= 1 & F <= 1 & G <= 1
The parentheses have no effect on the expression.

constraint with countPatientsForRoom {
        context.variables.patientCount >= 1
  	<=>
  	[
	  	mappings.selectedShiftToRoster->join((s, s))->sum(element.value) == 1
	  	&
	  	mappings.selectedShiftToRoster->join((r, r))->filter(element.nodes.vsr.shift.shiftNo == context.nodes.s.shiftNo + 1)->sum(element.value) == 1
		&
		mappings.selectedShiftToRoster->join((r, r))->filter(element.nodes.vsr.shift.shiftNo == context.nodes.s.shiftNo + 2)->sum(element.value) == 1
  	]
  	
  	&

  	// A room can only be assigned to 1 or 0 nurses for each shift
  	mappings.selectedShiftToRoster->join((s, s))->sum(element.value) <= 1
	&
	mappings.selectedShiftToRoster->join((r, r))->filter(
	    element.nodes.vsr.shift.shiftNo == context.nodes.s.shiftNo + 1
	)->sum(element.value) <= 1
	&
	mappings.selectedShiftToRoster->join((r, r))->filter(
	    element.nodes.vsr.shift.shiftNo == context.nodes.s.shiftNo + 2
	)->sum(element.value) <= 1
}
constraint with countPatientsForRoom {
        A >= 1
  	<=>
  	[ 
	  	B == 1
	  	&
	  	C == 1
		&
		D == 1
  	]  
  	
  	& 

  	E <= 1
	&
	F <= 1
	&
	G <= 1
}

2. Addition of another complex case

I will look into it!

@maxkratz
Copy link
Copy Markdown
Member

1. For one constraint, the validator did not let me use the new operator.

The linked constraint doesn't match any case.

It translates to: A >= 1 <-> [ B == 1 & C == 1 & D == 1 ] & E <= 1 & F <= 1 & G <= 1 The parentheses have no effect on the expression.

Oups, you are right! I could have sworn I checked the split of the respective constraint but ... apparently I didn't do it correctly.

After splitting it (maxkratz/dissertation-ihtp@3c8a063), every case of the new operators seems to work as expected.

Thanks again!

2. Addition of another complex case

I will look into it!

Thank you very much.

Copy link
Copy Markdown
Member

@maxkratz maxkratz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MarkBeB I have a few minor comments. What do you think?

@MarkBeB MarkBeB requested a review from maxkratz April 30, 2026 11:47
@MarkBeB
Copy link
Copy Markdown
Collaborator Author

MarkBeB commented Apr 30, 2026

Added a tooltip to the preference settings and added a comment to the comment!

Copy link
Copy Markdown
Member

@maxkratz maxkratz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you again for the nice implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce special transformations for operators => and <=> if (light weight) shortcut is possible

2 participants