您的位置:首页>软件开发>.NET>

通过 J# 求解约束条件

[ 来源:ccidnet | 更新日期:2007-7-15 20:19:34 | 评论 0 条 | 我要投稿 ]
通过本文中的有关 Visual J# .NET 的信息和示例设计一个表示方程式的系统,以便根据一个未知变量计算另一个变量。这是讨论如何通过 J# 解决问题的文章系列(包括三个部分)的最后一个部分。

注 该文章系列中的另外两个部分为第一部分使用 J# 的设计概念简介和第二部分委托与 J#,立刻行动起来。



本页内容
简介
问题陈述
解决方案域
设计练习

简介
在 CAD 系统中,一种常见的功能是能够在已经绘制的图形部分上指定数学条件,然后系统自动满足这些条件以使该图形采取所需的形状。例如,可以指定要求两个线段必须相互保持平行的条件。如果其中一条线段移动,则系统将以适当的方式移动另一条线段,以使它们继续相互保持平行。人们可以将涉及到的线段视为变量,将数学条件视为应用于这些变量的约束条件。约束条件限制了变量的自由度,也就是说,它约束了变量可以采取的值的范围。给定任意一条线段的位置,该系统可以求解 该约束条件以计算另一条线段的位置。 字串2

那些被组织为单向计算的计算机程序(它们对输入执行操作以产生需要的输出)并不适合于实现那些需要求解约束条件的系统。与摄氏温度和华氏温度有关的常见方程式说明了这一涉及到变量和约束条件的问题。

9c = 5(f - 32)

这样的方程式不是单向的。给定其中任何一个数量,我们可以使用它来计算另一个数量。假设我们具有一个方程式,其中 c 和 f 是变量,适用于它们的约束条件是数学条件加、减、乘、除(非常类似于 CAD 系统)。然而,将该方程式转换为程序将迫使我们根据其中一个数量来计算另一个数量。用于计算 c 的函数无法用来计算 f,尽管它们的计算产生于相同的方程式。

返回页首
问题陈述
设计一个表示上述方程式的系统,以便用来根据一个数量计算另一个数量。

问题域

我们具有一个方程式,其中 c 和 f 是变量,而适用于它们的约束条件是数学条件“加、减、乘、除”。在我们求解该方程式的过程中,将实施各种约束条件并计算中间值。如果用变量表示所有值(包括中间值),则可以按如下方式分解该方程式的左侧部分。
字串3


k = 9;
u = k * c;

还可以按如下方式为该方程式的右侧计算相同的积 u。

l = 5;
u = l * v;
m = 32;
f = m + v;

返回页首
解决方案域
我们的设计目标是能够按照变量和约束条件表示上述关系。

约束条件将在变量之间指定和实施某种特定关系。变量保留值并可能参与一个以上的约束条件。例如:add(x, y, z) 指定变量 x、y 和 z 的相互关系为 x + y = z;multiply(x, y, z) 指定关系 x*y = z;constant(5, x) 指定 x 的值必须为 5。我们将把上述约束条件组合起来,以构成能够表示整个方程式的约束网络。

我们创建了两个变量 ― c 和 f,并且用我们的约束网络将它们链接在一起。

variable c = new variable();
variable f = new variable();
cfconverter(c, f);
字串1

cfconverter 是摄氏-华氏网络。该网络按如下方式生成。
variable u = new variable();
variable k = new variable();
constant c1 = new constant(9, k);
multiply m1 = new multiply(c, k, u);

variable l = new variable();
constant c2 = new constant(5, l);
variable v = new variable();
multiply m2 = new multiply(v, l, u);

variable m = new variable();
constant c3 = new constant(32, m);
add a = new add(v, m, f);

变量 u、v、k、l、m 使用 add、multiply 和 constant 这些约束条件(对应于我们在前面分解该方程式的方式)相关联。在这样的网络上,如果我们现在可以设置 c 的值,则我们应当能够看到 f 的值被计算出来;反之亦然。

通过这样的网络进行的计算按如下方式进行:

字串5



当变量被给予一个值时,它会唤醒它的所有关联约束条件(刚刚唤醒它的约束条件除外),以通知它们它具有了一个值。每个被唤醒的约束条件都会轮询它的变量,以查看是否有足够的用来确定变量值的信息。如果有,则约束条件会设置该变量,后者随后会唤醒它的所有关联约束条件;依此类推。例如,在从摄氏温度到华氏温度的转换过程中,constant 约束条件立即将变量 k、l 和 m 的值分别设置为 9、5 和 32。变量唤醒乘法器和加法器,二者将确定没有继续计算所需的足够信息。当 c 的值被设置为某个数字 ― 例如 100 时,最左侧的乘法器被唤醒,并且将 u 设置为 100 * 9 = 900。然后,u 唤醒第二个乘法器,它将 v 设置为 180;v 唤醒加法器,它将 f 设置为 212。

变量的接口是通过下列方法实现的:

? boolean has_value()

分辨是否有值。

? double get_value()

返回该值。

? void set_value(double d, IConstraint theConstraint) 字串5

指示 theConstraint 希望将该值设置为 d。

? void reset(IConstraint theConstraint);

指示 theConstraint 希望重置该值。

? void connect(IConstraint theConstraint);

指示 theConstraint 希望该变量作为值参与。


变量回过头来与约束条件进行通信,以通知有关更新的信息。约束条件执行中间人 [1] 的角色,并且封装一组变量的关联方式。约束条件的接口是通过下列方法实现的:

? void valueChanged()

指示变量的值已经更改(并且存在新的值)。

? void reset()

指示变量的值已经重置。


constant 约束条件最为简单。它将它的变量设置为常量值,并且不关心 valueChanged 和 reset 方法。以下是我们的实现。 字串9

// file: IConstraint.jsl
interface IConstraint
{
void valueChanged();
void reset();
}

// file: constant.jsl
public class constant implements IConstraint
{
public constant(double d, variable v)
{
v.connect(this);
v.set_value(d, this);
}

public void valueChanged()
{
// no op
}

public void reset()
{
// no op
}
}

multiply 约束条件协调对下列三个变量的更新:multiplier、multiplicand 和 product。约束条件将其本身连接到上述变量,以便在它们的值被更新时得到通知。valueChanged 方法用于实施这三个变量之间的关系。类似地,每当其中一个变量报告它的值已经重置时,reset 方法就会重置所有变量。
字串1


// file: multiply.jsl
public class multiply implements IConstraint
{
private variable multiplier;
private variable multiplicand;
private variable product;

public multiply(variable v1, variable v2, variable v3)
{
multiplier = v1;
multiplicand = v2;
product = v3;

multiplier.connect(this);
multiplicand.connect(this);
product.connect(this);
}

public void valueChanged()
{
if (multiplier.has_value() && multiplicand.has_value())
{
product.set_value(multiplier.get_value() * multiplicand.get_value(), this);
}

字串7


else if (multiplier.has_value() && product.has_value())
{
if (multiplier.get_value() == 0.0)
{
multiplicand.set_value(0.0, this);
}
else
{
multiplicand.set_value(product.get_value() / multiplier.get_value(), this);
}
}
else if (multiplicand.has_value() && product.has_value())
{
if (multiplicand.get_value() == 0)
{
multiplier.set_value(0.0, this);
}
else
{
multiplier.set_value(product.get_value() / multiplicand.get_value(), this);
}
}
}
字串6
public void reset()
{
multiplier.reset(this);
multiplicand.reset(this);
product.reset(this);
}
}

add 约束条件与此类似。

// file: add.jsl
public class add implements IConstraint
{
private variable addend;
private variable augend;
private variable sum;

public add(variable v1, variable v2, variable v3)
{
addend = v1;
augend = v2;
sum = v3;

addend.connect(this);
augend.connect(this);
sum.connect(this);
}

public void valueChanged() 字串2
{
if (addend.has_value() && augend.has_value())
{
sum.set_value(addend.get_value() + augend.get_value(), this);
}
else if (addend.has_value() && sum.has_value())
{
augend.set_value(sum.get_value() - addend.get_value(), this);
}
else if (augend.has_value() && sum.has_value())
{
addend.set_value(sum.get_value() - augend.get_value(), this);
}
}

public void reset()
{
addend.reset(this);
augend.reset(this);
sum.reset(this);
}
}

作为其状态的一部分,变量保留值、它所参与的约束条件的列表以及用于设置它的值的约束条件。 字串6

// file: variable.jsl
import java.util.ArrayList;

public class variable
{
private boolean hasValue;
private double value;
private ArrayList constraints;
private IConstraint setter;

public boolean has_value()
{
return hasValue;
}

public double get_value()
{
return value;
}

public void set_value(double d, IConstraint theConstraint)
{
value = d;
hasValue = true;
setter = theConstraint;

for (int i = 0; i {

字串3


IConstraint m = (IConstraint) constraints.get(i);
if (m != setter)
{
m.valueChanged();
}
}
}

public void reset(IConstraint theConstraint)
{
if (theConstraint == setter)
{
value = 0.0;
hasValue = false;
setter = null;

// notify the mediators
for (int i = 0; i {
IConstraint m = (IConstraint) constraints.get(i);
if (m != setter)
{
m.reset();
}
}
}
}
字串8


public void connect(IConstraint theConstraint)
{
if (constraints == null)
{
constraints = new ArrayList();
}
constraints.add(theConstraint);
}
}

当 set_value 方法被调用时,它会设置该值,记住发出请求的约束条件,并通知其他所有参与约束条件。请不要忘记从接收通知的约束条件列表中排除 setter,以便使系统避免进入无限回归。类似地,reset 首先检查请求是否来自原来设置该值的同一约束条件;如果是,则重置它的值并通知其他所有参与约束条件。

我们引入了一个可以用来“查看”变量值的 tap。可以将 tap 视为反映变量值的被动约束条件。

// file: tap.jsl
public class tap implements IConstraint
{
private variable val;
private String name; 字串8

public tap(variable v, String s)
{
val = v;
name = s;

val.connect(this);
}

public void valueChanged()
{
System.out.println(name + ": " + val.get_value());
}

public void reset()
{
System.out.println(name + ": " + val.get_value());
}
}

我们现在可以挂钩该网络,并且将 tap 应用于我们想要监视的所有变量。

// file: cf.jsl
public class cf
{
public static void cfconverter(variable c, variable f)
{
variable u = new variable(); tap tu = new tap(u, "tu"); 字串8
variable k = new variable(); tap tk = new tap(k, "tk");
constant c1 = new constant(9, k);
multiply m1 = new multiply(c, k, u);

variable l = new variable(); tap tl = new tap(l, "tl");
constant c2 = new constant(5, l);

variable v = new variable(); tap tv = new tap(v, "tv");
multiply m2 = new multiply(v, l, u);

variable m = new variable(); tap tm = new tap(m, "tm");
constant c3 = new constant(32, m);
add a = new add(v, m, f);
}

public static void main(String[] args)
{
variable c = new variable(); tap tc = new tap(c, "c");

variable f = new variable(); tap tf = new tap(f, "f"); 字串6

cfconverter(c, f);

c.set_value(100, null);
c.reset(null);
f.set_value(32, null);
}
}

当变量 c 被设置为值 100 时,请注意计算是如何传播直到 f 的值被报告为 212 的。此时,称该网络是完全受约束的。只是将 f 设置为新的值不会使该网络计算 c。该网络必须重置,以便松开所有约束条件。然后,我们可以设置 f 的值,以便计算 c。

完毕。

返回页首
设计练习
? 如何表示线段?如何表示两条线段必须相互保持平行这一约束条件?


参考资料:

[1] Design Patterns: Elements of Reusable Object-Oriented Software??”Gamma E. et al. Addison Wesley, 1995


Tags:
责任编辑:
您的评论
用户名: 新注册) 密码: 匿名评论 [所有评论]

·用户发表意见仅代表其个人意见,并且承担一切因发表内容引起的纠纷和责任
·本站管理人员有权在不通知用户的情况下删除不符合规定的评论信息或留做证据
·请客观的评价您所看到的资讯,提倡就事论事,杜绝漫骂和人身攻击等不文明行为