diff --git a/lib/Parser/Context/Variables.pm b/lib/Parser/Context/Variables.pm index 243c2412..5021548c 100644 --- a/lib/Parser/Context/Variables.pm +++ b/lib/Parser/Context/Variables.pm @@ -19,6 +19,7 @@ our %type = ( 'Vector2D' => Value::Type('Vector', 2, $Value::Type{number}), 'Vector3D' => Value::Type('Vector', 3, $Value::Type{number}), 'Parameter' => $Value::Type{number}, + 'Constant' => $Value::Type{number}, ); sub init { @@ -46,6 +47,7 @@ sub create { ($value, @extra) = @{$value} if ref($value) eq 'ARRAY'; if (defined($type{$value})) { push(@extra, (parameter => 1)) if $value eq 'Parameter'; + push(@extra, (constant => 1)) if $value eq 'Constant'; $value = $type{$value}; } elsif (Value::isValue($value)) { $value = $value->typeRef; @@ -100,6 +102,14 @@ sub parameters { push(@names, $x) if $vars->{$x}{parameter} && !$vars->{$x}{alias}; } return @names; + } + +# +# Check if a variable is marked as a constant +# +sub isConstant { + my ($self, $name) = @_; + return $self->{context}{variables}{$name}{constant}; } ######################################################################### diff --git a/macros/contexts/contextInequalities.pl b/macros/contexts/contextInequalities.pl index 66721205..89a17c87 100644 --- a/macros/contexts/contextInequalities.pl +++ b/macros/contexts/contextInequalities.pl @@ -43,6 +43,19 @@ which allows only inequalities as a means of producing intervals. $S5 = Compute("x = 1"); # forms the Set $S6 = Compute("x != 1"); # forms the Union (-inf,1) U (1,inf) +It is possible to use a formula in place of the constant in +inequalities, by using special variables that are marked as constants. +E.g., + + Context("Inequalities"); + Context()->variables->add(a => "Constant"); + $S1 = Compute("x > 3a + 1"); + $s2 = Compute("a < x < 2a"); + +To do this, you must create a variable that is marked as a constant, +so it will be allowed to be used in the numeric part of the +inequality. + You can set the "noneWord" flag to specify the string to use when the inequalities specify the empty set. By default, it is "NONE", but you can change it to other strings. Be sure @@ -126,7 +139,7 @@ sub Init { type => 'bin', string => ' < ', class => 'Inequalities::BOP::inequality', - eval => 'evalLessThan', + method => 'LessThan', combine => 1 }, @@ -136,7 +149,7 @@ sub Init { type => 'bin', string => ' > ', class => 'Inequalities::BOP::inequality', - eval => 'evalGreaterThan', + method => 'GreaterThan', combine => 1 }, @@ -147,7 +160,7 @@ sub Init { string => ' <= ', TeX => '\le ', class => 'Inequalities::BOP::inequality', - eval => 'evalLessThanOrEqualTo', + method => 'LessThanOrEqualTo', combine => 1 }, '=<' => { @@ -157,7 +170,7 @@ sub Init { string => ' <= ', TeX => '\le ', class => 'Inequalities::BOP::inequality', - eval => 'evalLessThanOrEqualTo', + method => 'LessThanOrEqualTo', combine => 1, isSloppy => "<=" }, @@ -169,7 +182,7 @@ sub Init { string => ' >= ', TeX => '\ge ', class => 'Inequalities::BOP::inequality', - eval => 'evalGreaterThanOrEqualTo', + method => 'GreaterThanOrEqualTo', combine => 1 }, '=>' => { @@ -179,7 +192,7 @@ sub Init { string => ' >= ', TeX => '\ge ', class => 'Inequalities::BOP::inequality', - eval => 'evalGreaterThanOrEqualTo', + method => 'GreaterThanOrEqualTo', combine => 1, isSloppy => ">=" }, @@ -190,7 +203,7 @@ sub Init { type => 'bin', string => ' = ', class => 'Inequalities::BOP::inequality', - eval => 'evalEqualTo' + method => 'EqualTo' }, '!=' => { @@ -200,7 +213,7 @@ sub Init { string => ' != ', TeX => '\ne ', class => 'Inequalities::BOP::inequality', - eval => 'evalNotEqualTo' + method => 'NotEqualTo' }, 'and' => { @@ -218,6 +231,7 @@ sub Init { type => 'bin', string => " or ", TeX => '\hbox{ or }', + perl => '+', # equals union between intervals or sets class => 'Inequalities::BOP::or' }, ); @@ -241,6 +255,7 @@ sub Init { $context->{value}{InequalityUnion} = "Inequalities::Union"; $context->{value}{InequalitySet} = "Inequalities::Set"; $context->{value}{List} = "Inequalities::List"; + $context->{value}{Formula} = "Inequalities::Formula"; $context->{precedence}{Inequality} = $context->{precedence}{special}; $context->lists->set(List => { class => 'Inequalities::List::List' }); @@ -259,7 +274,31 @@ sub Init { # # Define the Inequality() constructor # - main::PG_restricted_eval('sub Inequality {Value->Package("Inequality")->new(@_)}'); + main::PG_restricted_eval('sub Inequality {Value->Package("Inequality()")->new(@_)}'); +} + +################################################## +# +# Override Value::Formula for inequalities so that +# addition can be used between two inequality formulas +# to implement "or" in perl code. +# +package Inequalities::Formula; +our @ISA = ('Value::Formula'); + +sub new { + my $self = shift; + my $f = $self->SUPER::new(@_); + bless $f, $self->Package("Formula") if ref($f->{tree}) =~ m/::inequality$/; + return $f; +} + +sub add { + my ($self, $l, $r) = Value::checkOpOrder(@_); + my $formula = $self->blank($self->context); + $formula->{tree} = $self->Item("BOP")->new($formula, "or", $l->{tree}, $r->{tree}); + $formula->{variables} = $formula->{tree}->getVariables; + return $formula; } ################################################## @@ -285,11 +324,13 @@ sub _check { if (!$self->context->flag("allowSloppyInequalities") && $self->{def}{isSloppy}); $self->{type} = $Value::Type{interval}; $self->{isInequality} = 1; + my $variables = $self->context->variables; ($self->{varPos}, $self->{numPos}) = - ($self->{lop}->class eq 'Variable' || $self->{lop}{isInequality} ? ('lop', 'rop') : ('rop', 'lop')); + ($self->{lop}->class eq 'Variable' && !$variables->isConstant($self->{lop}{name})) || + $self->{lop}{isInequality} ? ('lop', 'rop') : ('rop', 'lop'); my ($v, $n) = ($self->{ $self->{varPos} }, $self->{ $self->{numPos} }); - if (($n->isNumber || $n->{isInfinite}) && ($n->{isConstant} || scalar(keys %{ $n->getVariables }) == 0)) { - if ($v->class eq 'Variable') { + if (($n->isNumber || $n->{isInfinite}) && $self->checkConstant($n)) { + if ($v->class eq 'Variable' && !$variables->isConstant($v->{name})) { $self->{varName} = $v->{name}; delete $self->{equation}{variables}{ $v->{name} } if $v->{isNew}; $self->{ $self->{varPos} } = Inequalities::DummyVariable->new($self->{equation}, $v->{name}, $v->{ref}); @@ -311,6 +352,18 @@ sub _check { $self->Error("'%s' can't be combined with '%s'", $v->{bop}, $self->{bop}); } +# +# Check that all variables in $n are marked as constants +# +sub checkConstant { + my ($self, $n) = @_; + my $variables = $self->context->variables; + for my $x (keys %{$n->getVariables}) { + return 0 if !$variables->isConstant($x); + } + return 1; +} + # # Generate the interval for the given type of inequality. # If it is a combined inequality, intersect with the other @@ -319,7 +372,7 @@ sub _check { sub _eval { my $self = shift; my ($a, $b) = @_; - my $eval = $self->{def}{eval}; + my $eval = 'eval' . $self->{def}{method}; my $I = $self->Package("Inequality")->new($self->context, $self->$eval(@_), $self->{varName}); return $I->intersect($a) if Value::isValue($a) && $a->type eq 'Interval'; return $I->intersect($b) if Value::isValue($b) && $b->type eq 'Interval'; @@ -417,9 +470,13 @@ sub evalNotEqualTo { # # Inequalities have dummy variables that are not really -# variables of a formula. - -sub getVariables { {} } +# variables of a formula, so get only the variables from +# the other side of the inequality. +# +sub getVariables { + my $self = shift; + return $self->{$self->{numPos}}->getVariables; +} # # Avoid unwanted parentheses from the standard routines. @@ -447,6 +504,79 @@ sub TeX { return $TeX; } +sub perl { + my $self = shift; + my $perl = 'perl' . $self->{def}{method}; + return $self->$perl($self->{lop}->perl, $self->{rop}->perl); +} + +sub perlLessThan { + my ($self, $a, $b) = @_; + my $I = $self->Infinity->perl; + my $x = $self->{varName}; + my $INTERVAL = $self->Package("InequalityInterval"); + if ($self->{varPos} eq 'lop') { + return "$INTERVAL->new('(', -$I, $b, ')')->with(varName => '$x')"; + } else { + return "$INTERVAL->new('(', $a, $I, ')')->with(varName => '$x')"; + } +} + +sub perlGreaterThan { + my ($self, $a, $b) = @_; + my $I = $self->Infinity->perl; + my $x = $self->{varName}; + my $INTERVAL = $self->Package("InequalityInterval"); + if ($self->{varPos} eq 'lop') { + return "$INTERVAL->new('(', $b, $I , ')')->with(reversed => 1, varName => '$x')"; + } else { + return "$INTERVAL->new('(', -$I, $a, ')')->with(reversed => 1, varName => '$x')"; + } +} + +sub perlLessThanOrEqualTo { + my ($self, $a, $b) = @_; + my $I = $self->Infinity->perl; + my $x = $self->{varName}; + my $INTERVAL = $self->Package("InequalityInterval"); + if ($self->{varPos} eq 'lop') { + return "$INTERVAL->new('(', -$I, $b, ']')->with(varName => '$x')"; + } else { + return "$INTERVAL->new('[', $a, $I, ')')->with(varName => '$x')"; + } +} + +sub perlGreaterThanOrEqualTo { + my ($self, $a, $b) = @_; + my $I = $self->Infinity->perl; + my $x = $self->{varName}; + my $INTERVAL = $self->Package("InequalityInterval"); + if ($self->{varPos} eq 'lop') { + return "$INTERVAL->new('[', $b, $I, ')')->with(reversed => 1, varName => '$x')"; + } else { + return "$INTERVAL->new('(', -$I, $a, ']')->with(reversed => 1, varName => '$x')"; + } +} + +sub perlEqualTo { + my ($self, $a, $b) = @_; + my $x = $self->{varName}; + my $n = ($self->{numPos} eq 'lop' ? $a : $b); + return $self->Package("InequalitySet") . "->new($n)->with(varName => '$x')"; +} + +sub perlNotEqualTo { + my ($self, $a, $b) = @_; + my $x = $self->{varName}; + my $n = ($self->{numPos} eq 'lop' ? $a : $b); + my $I = $self->Infinity->perl; + my $INTERVAL = $self->Package("InequalityInterval"); + my $l = "$INTERVAL->new('(', -$I, $n, ')')"; + my $r = "$INTERVAL->new('(', $n, $I, ')')"; + return $self->Package("InequalityUnion") . "->new($l, $r)->with(notEqual => 1, varName => '$x')"; +} + + ################################################## # # Implements the "and" operation as set intersection @@ -467,6 +597,12 @@ sub _check { sub _eval { $_[1]->intersect($_[2]) } +sub perl { + my $self = shift; + my ($l, $r) = ($self->{lop}->perl, $self->{rop}->perl); + return "$l->intersect($r)"; +} + ################################################## # # Implements the "or" operation as set union @@ -485,7 +621,7 @@ sub _check { $self->{isInequality} = 1; } -sub _eval { $_[1] + $_[2] } +sub _eval {$_[1] + $_[2]} ################################################## # @@ -560,6 +696,7 @@ sub _check { $self->Error("Unions are not allowed in this context"); } + ################################################## # # Don't allow sums and differences of inequalities @@ -759,6 +896,7 @@ sub TeX { } } + ################################################## package Inequalities::Union; diff --git a/macros/contexts/contextInequalitySetBuilder.pl b/macros/contexts/contextInequalitySetBuilder.pl index 72799d90..b7463b8f 100644 --- a/macros/contexts/contextInequalitySetBuilder.pl +++ b/macros/contexts/contextInequalitySetBuilder.pl @@ -50,6 +50,19 @@ The C contexts accept the flags for the Inequalities contexts from the C file (see its documentation for details). +It is possible to use a formula in place of the constant in the +inequalities involced in a set by using special variables that are +marked as constants. E.g., + + Context("InequalitySetBuilder"); + Context()->variables->add(a => "Constant"); + $S1 = Compute("{x : x > 3a + 1}"); + $s2 = Compute("{x : a < x < 2a}"); + +To do this, you must create a variable that is marked as a constant, +so it will be allowed to be used in the numeric part of the +inequality. + Set-builder and interval notation both can coexist side by side, but you may wish to convert from one to the other. Use C to convert from an Interval, Set or Union to an Inequality, and use @@ -57,7 +70,7 @@ C to convert from an Inequality object to one in interval notation. For example: $I0 = Compute("(1,2]"); # the interval (1,2] - $I1 = SetBuilder($I1); # the set { x : 1 < x <= 2 } + $I1 = SetBuilder($I0); # the set { x : 1 < x <= 2 } $I0 = Compute("{ x : 1 < x <= 2 }"); # the set { x : 1 < x <= 2 } $I1 = Interval($I0); # the interval (1,2] @@ -183,7 +196,7 @@ sub new { if (defined($x)) { $S->{varName} = $x; $S->updateParts } return $S; } - $x = ($context->variables->names)[0] unless $x; + $x = $self->getVarName($context) unless $x; $S = bless $S->inContext($context), $context->Package("InequalitySetBuilder" . $S->type); $S->{varName} = $x; $S->{reduceSets} = $S->{isInequality} = $S->{ "is" . $S->Type } = 1; @@ -191,6 +204,19 @@ sub new { return $S; } +# +# Find first non-constant variable +# +sub getVarName { + my ($self, $context) = @_; + my $variables = $context->variables; + for my $name ($variables->names) { + if (!$variables->isConstant($name)) { + return $name; + } + } +} + ################################################## ################################################## # @@ -227,6 +253,14 @@ sub eval { } } +sub perl { + my $self = shift; + my $rop = $self->{coords}[0]{rop}; + my $perl = $rop->perl; + my $x = $rop->{varName}; + return $self->Package('InequalitySetBuilder' . $rop->type) . "->new($perl)->with(varName => '$x')"; +} + sub canBeInUnion {1} ################################################## @@ -240,7 +274,7 @@ our @ISA = ('Parser::BOP'); sub _check { my $self = shift; $self->Error("%s should follow a variable name", $self->{bop}) - unless $self->{lop}->class eq 'Variable'; + unless $self->{lop}->class eq 'Variable' && !$self->context->variables->isConstant($self->{lop}{name}); $self->Error("The condition for the set should be an inequality") unless $self->{rop}{isInequality}; $self->Error("The variable of the condition to the right of %s should match the variable on the left", $self->{bop}) @@ -297,6 +331,7 @@ sub _check { my $self = shift; if ($self->{lop}{isSetBuilder} && $self->{rop}{isSetBuilder}) { $self->{isSetBuilder} = 1 } else { $self->SUPER::_check(@_) } + $self->{type} = Value::Type("Union") unless $self->{type}; } ################################################## @@ -308,6 +343,12 @@ sub _check { package InequalitySetBuilder::common; our @ISA = (); +sub new { + my $self = shift; + $self = $self->Package($self->type)->new(@_); + return $self->Package('SetBuilder')->new($self); +} + sub _updateParts { my $self = shift; $self->{isSetBuilder} = 1;