nocte- / rhea Goto Github PK
View Code? Open in Web Editor NEWA constraint solver based on Cassowary
License: MIT License
A constraint solver based on Cassowary
License: MIT License
This looks so clean, great job. I am going to try it. Any updates on whether you think it could be used in production? Also, is it thread safe? I am going to try in a Mac app and when I detect that multiple layout problems could be solved in parallel I was going to spin off a few jobs to do so.
Hi!
Just read this in the README:
This software is alpha. It does pass all unit tests, but it hasn't been used in an actual application yet. Use at your own peril.
I wonder if this is still accurate, knowing that it is being used in Hexahedra. I want to use the Cassowary in some real software in production code, and I was tempted to use this library instead, which looks so much better and in agreement with my modern C++ moral values (hehe). This warning just left me wondering if it would be just a waste of time or not...
Can you add a tag e.g. '0.1' to this repo? It would be great for my build environment.
To get an understanding of the behavior I'm seeing I think it is best to see this video.
The constraint is very simple – I can't understand why it behaves so jaggy. Also notice how the content gets totally offset from its content while I'm holding the mouse down.
Code
static rhea::simplex_solver solver;
static rhea::variable blueLeft (10);
static rhea::variable blueTop (10);
static rhea::variable blueWidth (100);
static rhea::variable blueHeight (100);
static rhea::variable greenLeft (220);
static rhea::variable greenTop (10);
static rhea::variable greenHeight (100);
static rhea::variable greenWidth (100);
static rhea::variable containerWidth (320);
static rhea::variable containerHeight (480);
@interface ViewController ()
@property (nonatomic, strong) UIView *blueView;
@property (nonatomic, strong) UIView *greenView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
solver.set_autosolve(false);
solver.add_stay(blueLeft).add_stay(blueTop).add_stay(blueWidth).add_stay(blueHeight);
solver.add_stay(greenLeft).add_stay(greenTop).add_stay(greenWidth).add_stay(greenHeight);
solver.add_stay(containerWidth).add_stay(containerHeight);
solver.add_constraints({
greenTop == blueTop,
blueLeft >= 10,
});
solver.on_resolve = [self](rhea::simplex_solver &solver) {
[self updateFrames];
};
}
- (void)setupViews
{
self.blueView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
self.blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.blueView];
UIPanGestureRecognizer *blueRecognizer = [UIPanGestureRecognizer new];
[blueRecognizer addTarget:self action:@selector(blueRecognizerUpdated:)];
[self.blueView addGestureRecognizer:blueRecognizer];
self.greenView = [[UIView alloc] initWithFrame:CGRectMake(220, 0, 200, 200)];
self.greenView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.greenView];
UIPanGestureRecognizer *greenRecognizer = [UIPanGestureRecognizer new];
[greenRecognizer addTarget:self action:@selector(greeRecognizerUpdated:)];
[self.greenView addGestureRecognizer:greenRecognizer];
}
- (void)blueRecognizerUpdated:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view.superview];
[recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
solver.suggest({
{ blueLeft, blueLeft.value() + translation.x },
{ blueTop, blueTop.value() + translation.y }
});
solver.solve();
}
- (void)greeRecognizerUpdated:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view.superview];
[recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
solver.suggest({
{ greenLeft, greenLeft.value() + translation.x },
{ greenTop, greenTop.value() + translation.y }
});
solver.solve();
}
- (void)updateFrames
{
CGRect blueFrame;
blueFrame.origin.x = blueLeft.value();
blueFrame.origin.y = blueTop.value();
blueFrame.size.width = blueWidth.value();
blueFrame.size.height = blueHeight.value();
self.blueView.frame = blueFrame;
CGRect greenFrame;
greenFrame.origin.x = greenLeft.value();
greenFrame.origin.y = greenTop.value();
greenFrame.size.width = greenWidth.value();
greenFrame.size.height = greenHeight.value();
self.greenView.frame = greenFrame;
}
@end
solver& set_autosolve(bool is_auto = true)
{
auto_solve_ = true;
if (auto_solve_)
solve();
return *this;
}
I would expect it to be something like this
solver& set_autosolve(bool is_auto = true)
{
auto_solve_ = is_auto;
if (auto_solve_)
solve();
return *this;
}
When I'm trying to include rhea I'm getting error:
"Invalid application of 'sizeof' to an incomplete type 'rhea::simplex_solver'"
because simplex_solver don't have on_resolve and on_variable_change implemented
Some of the unit tests expect an 'required_failure' exception, but the library happily accepts the contradictory constraints.
In python they have named their variables.
http://cassowary.readthedocs.org/en/latest/topics/theory.html#variables
In my framework I have a map of variables with names as key. Is this something that we should have in Rhea as well? I guess it is useful when logging and when exceptions is thrown. For me at least the map is sufficient.
Edit: I'm not saying we should have a map in Rhea. Just asking wether we should have a name property in the rhea variable.
Continuing the discussion from https://github.com/Nocte-/rhea/issues/25#issuecomment-64810478 here. Should we A) fork http://cassowary.readthedocs.org/en/latest/ and replace it with Rhea/C++ syntax or B) should we try to improve the python documentation?
I'm not sure if this is a bug or not. I wrote some unit tests derived from the test required_strength
which I expect to pass. Am I missing something?
BOOST_AUTO_TEST_CASE(required_strength_stay)
{
variable v(0);
simplex_solver solver;
solver.add_stay(v, strength::required(), 1);
BOOST_CHECK_EQUAL(v.value(), 0);
solver.add_edit_var(v, strength::required(), 2);
solver.begin_edit();
solver.suggest_value(v, 2);
solver.end_edit();
BOOST_CHECK_EQUAL(v.value(), 2);
}
BOOST_AUTO_TEST_CASE(required_strength_stay_and_constraint)
{
variable v(0), y(0);
simplex_solver solver;
solver.add_stay(v, strength::required(), 2);
BOOST_CHECK_EQUAL(v.value(), 0);
BOOST_CHECK_EQUAL(y.value(), 2);
solver.add_edit_var(v, strength::required(), 3);
solver.begin_edit();
solver.suggest_value(v, 2);
solver.end_edit();
solver.add_constraint(y == v + 2);
BOOST_CHECK_EQUAL(v.value(), 2);
BOOST_CHECK_EQUAL(y.value(), 4);
}
It would be absolutely lovely to be able to write
rhea::variable containerHeight (10);
containerHeight.on_change = [self](const rhea::variable &v) {
std::cout << "containerHeight changed to: " << v.value() << std::endl;
};
Instead of
solver.on_variable_change = [self](const rhea::variable& v, rhea::simplex_solver& s) {
if(v.is(containerHeight)) {
std::cout << "containerHeight changed to: " << v.value() << std::endl;
}
}
The disadvantage with the current API is that a growing number of variables will also lead the a growing number of if's.
What do you think?
How do I create a hashmap of variables where string is key?
I rather ask you than have something work by chance.
Pseudo code
std::unordered_map<std::string, rhea::variable> _variableMap;
- (rhea::variable)variableByName:(NSString *)name
{
std::string key([name UTF8String]);
rhea::variable variable = _variableMap[key];
if(variable.is_nil())
{
variable = rhea::variable(0);
_variableMap[key] = variable;
}
return variable;
}
Hi!
I have a couple of questions regarding the edit protocol. The first one is a exception I stumbled upon today when adding a edit constraint on a variable that has no constraints. I got an edit_misuse
exception thrown in add_constraint_
because this condition is not met !is_basic_var(v) && !columns_has_key(v)
. I wonder if this is really necessary (it seems conceptually feasible to edit an unconstrained variable).
Also, I have problems in my code because edits are not exactly stacked. Let's think for example of a multi-touch environment and a program where two dots can be moved with the fingers. The edits of the first and second dot might interleave, so the trivial association of touch/release to begin/end-edit does not work for situations like touch(p1) -> touch(p2) -> release(p1) -> release(p2)
. I wonder if a valid alternative is to have one single big edit around the whole program that guards all editable variables. Otherwise, could you hint on a better alternative within rhea or, alternatively, how could I change rhea to work this way?
In the lines of the later, I considered returning the identifier the edit_info_list_size_
in begin_edit
and optionally taking this as an argument in end_edit
to identify which variables exactly to remove.
What do you think?
Thanks!
Users that don't use variable names shouldn't have to pay for it. If an application needs named variables, it trivial to implement this with a (bi)map.
I looked into these as a more stable way of changing weights and strengths than just removing and adding the constraint again. Sadly, this method seems to have no effect and looking into unit_tests.cpp
it is untested too.
Hi,
I'm studying with this library by rewriting a new Lua version of Cassowary algorithm. It's real good example of how to use C++11 to write neat code. but I think maybe I found a little glitch in simplex_solver.cpp#L532:
// v is restricted. If we have already found a suitable
// restricted variable just stick with that. Otherwise, if v
// is new to the solver and has a negative coefficient pick
// it. Regarding being new to the solver -- if the variable
// occurs only in the objective function we regard it as being
// new to the solver, since error variables are added to the
// objective function when we make the Expression. We also
// never pick a dummy variable here.
if (!found_new_restricted && !v.is_dummy() && c < 0.0) {
auto i(columns_.find(v));
if (i == columns_.end()
|| (columns_.size() == 1
&& columns_has_key(objective_))) {
subj = v;
found_new_restricted = true;
}
}
If I'm not misunderstanding, columns_
is a map with the parameter variable and the set of basic variable which expression contain this parameter variable. so to see the v
is only occurs in objective function, maybe we should found objective_
in *i
, which is the basic variable set, but not in columns_
mapping.
When running with this code
static rhea::simplex_solver solver;
static rhea::variable testValue (10);
solver.add_stay(testValue);
solver.add_constraints({
testValue >= 200,
});
solver.on_variable_change = [self](const rhea::variable& v, rhea::simplex_solver& s) {
if(v.is(testValue)) {
std::cout << "on_variable_change " << v.value() << std::endl;
}
};
solver.on_resolve = [self](rhea::simplex_solver &solver) {
std::cout << "on_resolve" << std::endl;
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
solver.suggest({ { testValue, 500 } });
});
I get this output
on_variable_change 200
on_resolve
on_variable_change 500
on_resolve
on_variable_change 500
on_resolve
From a user point of view it would be beneficial if it was one callback less like this
on_variable_change 200
on_resolve
on_variable_change 500
on_resolve
The stack trace for the on_resolve
callback is
The library really needs an expression parser module. Look into boost.spirit.
Hi
Can we change the license to Apache like some of the other projects did?
https://groups.google.com/forum/#!searchin/overconstrained/license/overconstrained/rqoXuonGGkc/TEfXMQ31k8sJ
Hi
When dealing with constriants like
textLeftPosition == containerWidth * 0.1,
textWidth == containerWidth * 0.8
The text quickly gets blury due to subpixel positioning? Is there any way in Rhea / Cassowary to say that I'd rather have the values rounded? Or should this logic reside outside of Rhea / Cassowary?
ObjC has nil defined as
#ifndef nil
# if __has_feature(cxx_nullptr)
# define nil nullptr
# else
# define nil __DARWIN_NULL
# endif
#endif
and you have
class variable {
static variable nil() { return variable(); }
}
it breaks if you try to include rhea
Here is the sample project which is reproducing the problem: https://github.com/har-gup/rhea-constant-issue.
The same issue doesn't happen if we use edit variable in a constraint instead of constant.
I tried debugging the issue, and saw that set_constant_ method is returning from https://github.com/Nocte-/rhea/blob/0.3/rhea/simplex_solver.cpp#L184 without adding any row to infeasible_rows_, and therefore dual_optimize function is not called.
@Nocte- If we change following lines https://github.com/Nocte-/rhea/blob/0.3/rhea/simplex_solver.cpp#L164 and https://github.com/Nocte-/rhea/blob/0.3/rhea/simplex_solver.cpp#L202 to
auto delta = -(constant - evs.prev_constant);
, and expr.add(expr.coefficient(evs.marker) * delta);
respectively, the way it is in suggest_value_ function https://github.com/Nocte-/rhea/blob/0.3/rhea/simplex_solver.cpp#L265 then it works fine.
We discussed this briefly on slack. "it can only be done as part of the library, since rhea::variable needs to know about them."
The reason I want to subclass variables is to deny users from using the same variable in multiple solvers.
My setup is like this:
A container can have many nodes. Each node has its own set of variables (left, right, bottom, top, etc). Each container has its own solver. A node can be a container. And here's the catch: the user should only use container-specific variables like contentHeight and contentWidth between the container and its nodes, not between siblings and parent container.
Any ideas?
Sometimes, an edit constraint is not cleaned up automatically by edit_end().
Reproducible sample: https://github.com/har-gup/rhea-issue
Expectations: I am expecting greenView
to be positioned wherever blueView
is positioned with an offset of 210 on the x-axis.
solver.add_constraints({
greenLeft == blueLeft + 210,
});
Result: blueView
is not moving when suggesting new values.
Code
#import "ViewController.h"
#include "rhea/simplex_solver.hpp"
#include "rhea/iostream.hpp"
static rhea::simplex_solver solver;
static rhea::variable blueLeft (10), blueTop (10), blueWidth (200), blueHeight (200);
static rhea::variable greenLeft (10), greenTop (10), greenWidth (200), greenHeight (200);
static rhea::variable boundsWidth (1000), boundsHeight (768);
@interface ViewController ()
@property (nonatomic, strong) UIView *blueView;
@property (nonatomic, strong) UIView *greenView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
[self setupConstraints];
[self updateFrames];
}
- (void)setupViews
{
self.blueView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 200, 200)];
self.blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.blueView];
UIPanGestureRecognizer *blueRecognizer = [UIPanGestureRecognizer new];
[blueRecognizer addTarget:self action:@selector(blueRecognizerUpdated:)];
[self.blueView addGestureRecognizer:blueRecognizer];
self.greenView = [[UIView alloc] initWithFrame:CGRectMake(220, 10, 200, 200)];
self.greenView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.greenView];
UIPanGestureRecognizer *greenRecognizer = [UIPanGestureRecognizer new];
[greenRecognizer addTarget:self action:@selector(greeRecognizerUpdated:)];
[self.greenView addGestureRecognizer:greenRecognizer];
}
- (void)setupConstraints
{
solver.set_autosolve(false);
solver.add_constraints({
greenLeft == blueLeft + 210,
});
solver.add_constraints({
greenTop == blueTop,
});
solver.solve();
}
- (void)updateFrames
{
CGRect blueFrame;
blueFrame.origin.x = blueLeft.value();
blueFrame.origin.y = blueTop.value();
blueFrame.size.width = blueWidth.value();
blueFrame.size.height = blueHeight.value();
self.blueView.frame = blueFrame;
CGRect greenFrame;
greenFrame.origin.x = greenLeft.value();
greenFrame.origin.y = greenTop.value();
greenFrame.size.width = greenWidth.value();
greenFrame.size.height = greenHeight.value();
self.greenView.frame = greenFrame;
}
- (void)blueRecognizerUpdated:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view.superview];
[recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
solver.suggest({
{ blueLeft, self.blueView.frame.origin.x + translation.x },
{ blueTop, self.blueView.frame.origin.y + translation.y }
});
solver.solve();
[self updateFrames];
}
- (void)greeRecognizerUpdated:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view.superview];
[recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
solver.suggest({
{ greenLeft, self.greenView.frame.origin.x + translation.x },
{ greenTop, self.greenView.frame.origin.y + translation.y }
});
solver.solve();
[self updateFrames];
}
@end
I'm doing some research into the various implementations of the Cassowary algorithm to spot their differences. Part of this process is comparing tests for these implementations. Upon porting one of the unit tests from the javascript port at https://github.com/slightlyoff/cassowary.js/ I noticed that Rhea crashes when dealing with stay constraints having zero weight.
The failing test case is:
BOOST_AUTO_TEST_CASE (required_strength)
{
variable variable(0);
simplex_solver solver;
constraint constraint(std::make_shared<stay_constraint>(variable, strength::strong(), 0));
solver.add_constraint(constraint);
solver.resolve();
BOOST_CHECK_EQUAL(variable.value(), 0);
solver.add_edit_var(variable, strength::required());
solver.begin_edit();
solver.suggest_value(variable, 2);
solver.resolve();
BOOST_CHECK_EQUAL(variable.value(), 2);
}
The backtrace I got:
* thread #1: tid = 0x19edf7, 0x00000001000469f2 librhea.1.dylib`rhea::linear_expression::substitute_out(rhea::variable const&, rhea::linear_expression const&, rhea::variable const&, rhea::tableau&) + 162, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
* frame #0: 0x00000001000469f2 librhea.1.dylib`rhea::linear_expression::substitute_out(rhea::variable const&, rhea::linear_expression const&, rhea::variable const&, rhea::tableau&) + 162
frame #1: 0x0000000100055379 librhea.1.dylib`rhea::tableau::substitute_out(rhea::variable const&, rhea::linear_expression const&) + 313
frame #2: 0x000000010004b13a librhea.1.dylib`rhea::simplex_solver::pivot(rhea::variable const&, rhea::variable const&) + 74
frame #3: 0x000000010004c943 librhea.1.dylib`rhea::simplex_solver::optimize(rhea::variable const&) + 659
frame #4: 0x0000000100049661 librhea.1.dylib`rhea::simplex_solver::add_with_artificial_variable(rhea::linear_expression&) + 241
frame #5: 0x0000000100048ea7 librhea.1.dylib`rhea::simplex_solver::add_constraint_(rhea::constraint const&) + 647
frame #6: 0x000000010001cd4e unit_tests`rhea::simplex_solver::add_edit_var(rhea::variable const&, rhea::strength const&, double) + 254
frame #7: 0x000000010001a6c1 unit_tests`required_strength::test_method() + 785
frame #8: 0x0000000100080781 libboost_unit_test_framework-mt.dylib`boost::unit_test::ut_detail::callback0_impl_t<int, boost::unit_test::(anonymous namespace)::zero_return_wrapper_t<boost::unit_test::callback0<boost::unit_test::ut_detail::unused> > >::invoke() + 17
frame #9: 0x0000000100065850 libboost_unit_test_framework-mt.dylib`boost::execution_monitor::catch_signals(boost::unit_test::callback0<int> const&) + 160
frame #10: 0x00000001000658f7 libboost_unit_test_framework-mt.dylib`boost::execution_monitor::execute(boost::unit_test::callback0<int> const&) + 39
frame #11: 0x000000010008055f libboost_unit_test_framework-mt.dylib`boost::unit_test::unit_test_monitor_t::execute_and_translate(boost::unit_test::test_case const&) + 159
frame #12: 0x0000000100072097 libboost_unit_test_framework-mt.dylib`boost::unit_test::framework_impl::visit(boost::unit_test::test_case const&) + 151
frame #13: 0x00000001000a478e libboost_unit_test_framework-mt.dylib`boost::unit_test::traverse_test_tree(boost::unit_test::test_suite const&, boost::unit_test::test_tree_visitor&) + 350
frame #14: 0x000000010006ee25 libboost_unit_test_framework-mt.dylib`boost::unit_test::framework::run(unsigned long, bool) + 4181
frame #15: 0x000000010007ec03 libboost_unit_test_framework-mt.dylib`boost::unit_test::unit_test_main(bool (*)(), int, char**) + 211
frame #16: 0x00007fff89c5e5fd libdyld.dylib`start + 1
Hi!
So... as discussed on Slack, I have been having trouble with stays not really having the desired effect all of the time. Indeed, quite often, when I add or remove constraints, variables jump around even though they should not. This have driven me crazy for some time and led to resort to all kinds of hacks. Because I use Rhea via aqt-cassowary [1], this has been very hard to reproduce -- this library usually manipulate the solver in non-deterministic, but in theory correct, orders.
So, finally I have been able to write a test that fails sistematically in my machine here. I have two variations of the test, that execute the sames calls in different order. In one it fails, in the other it passes. Funny thing is, the failing test requires only to change the order in which variables are declared just to make it pass. Crazy shit.
So, here are the tests:
arximboldi@adbfdf5
I am most certainly missing something :-)
Much like constants there are some things that should never be updated by the solver. For instance the dimension of the screen on the device. The only time the dimension of the screen changes is when the user is rotating thus flipping the width and height.
Any thoughts? Should I play around with the strength of variable and constraints?
Expectations: I am expecting the blueTop
and greenTop
to initially be resolved to 10 and then later resolved to whatever I suggest.
Result: The constraint is resolved 0 all the time
#import "ViewController.h"
#include "rhea/simplex_solver.hpp"
#include "rhea/iostream.hpp"
static rhea::simplex_solver solver;
static rhea::variable blueLeft (10), blueTop (10), blueWidth (200), blueHeight (200);
static rhea::variable greenLeft (10), greenTop (10), greenWidth (200), greenHeight (200);
static rhea::variable boundsWidth (1000), boundsHeight (768);
@interface ViewController ()
@property (nonatomic, strong) UIView *blueView;
@property (nonatomic, strong) UIView *greenView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
[self setupConstraints];
[self updateFrames];
}
- (void)setupViews
{
self.blueView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 200, 200)];
self.blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.blueView];
UIPanGestureRecognizer *blueRecognizer = [UIPanGestureRecognizer new];
[blueRecognizer addTarget:self action:@selector(blueRecognizerUpdated:)];
[self.blueView addGestureRecognizer:blueRecognizer];
self.greenView = [[UIView alloc] initWithFrame:CGRectMake(220, 10, 200, 200)];
self.greenView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.greenView];
UIPanGestureRecognizer *greenRecognizer = [UIPanGestureRecognizer new];
[greenRecognizer addTarget:self action:@selector(greeRecognizerUpdated:)];
[self.greenView addGestureRecognizer:greenRecognizer];
}
- (void)setupConstraints
{
solver.set_autosolve(false);
solver.add_constraints({
blueLeft >= 20,
});
solver.add_constraints({
greenLeft <= 400,
});
solver.add_constraints({
greenTop == blueTop,
});
solver.solve();
}
- (void)updateFrames
{
CGRect blueFrame;
blueFrame.origin.x = blueLeft.value();
blueFrame.origin.y = blueTop.value();
blueFrame.size.width = blueWidth.value();
blueFrame.size.height = blueHeight.value();
self.blueView.frame = blueFrame;
CGRect greenFrame;
greenFrame.origin.x = greenLeft.value();
greenFrame.origin.y = greenTop.value();
greenFrame.size.width = greenWidth.value();
greenFrame.size.height = greenHeight.value();
self.greenView.frame = greenFrame;
}
- (void)blueRecognizerUpdated:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view.superview];
[recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
solver.suggest({
{ blueLeft, self.blueView.frame.origin.x + translation.x },
{ blueTop, self.blueView.frame.origin.y + translation.y }
});
solver.solve();
[self updateFrames];
}
- (void)greeRecognizerUpdated:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view.superview];
[recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
solver.suggest({
{ greenLeft, self.greenView.frame.origin.x + translation.x },
{ greenTop, self.greenView.frame.origin.y + translation.y }
});
solver.solve();
[self updateFrames];
}
@end
Describe functions and classes with Doxygen comments.
I have tried your library, but it just does not work. This is my code:
//
// AFRheaTestView.m
// FlexSchematics
//
// Created by Phillip Schuster on 18.11.15.
// Copyright © 2015 Appfruits. All rights reserved.
//
#import "AFRheaTestView.h"
#include "rhea/simplex_solver.hpp"
#include "rhea/iostream.hpp"
#include "rhea/variable.hpp"
@interface AFRheaTestView ()
@property (nonatomic, strong) NSTrackingArea* trackingArea;
@property (nonatomic, assign) BOOL dragged;
@property (nonatomic, assign) rhea::variable l1X;
@property (nonatomic, assign) rhea::variable l1Y;
@property (nonatomic, assign) rhea::variable r1X;
@property (nonatomic, assign) rhea::variable r1Y;
@property (nonatomic, assign) rhea::simplex_solver solver;
@end
@implementation AFRheaTestView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
[[NSColor magentaColor] setStroke];
NSBezierPath* bezierPath = [NSBezierPath bezierPath];
[bezierPath moveToPoint:NSMakePoint(self.l1X.value(), self.l1Y.value())];
[bezierPath lineToPoint:NSMakePoint(self.r1X.value(), self.r1Y.value())];
[bezierPath stroke];
}
-(void)awakeFromNib
{
// self.l1X.set_value(100);
// self.l1Y.set_value(100);
self.solver.set_autosolve(false);
self.solver.on_resolve = [self](rhea::simplex_solver &solver) {
std::cout << "on_resolve" << std::endl;
};
self.solver.on_variable_change = [self](const rhea::variable& v, rhea::simplex_solver& s) {
std::cout << "on_variable_change " << v.value() << std::endl;
};
self.solver.add_stay(_l1X);
self.solver.add_stay(_l1Y);
self.solver.set_explaining(true);
self.solver.add_constraint(_r1X == _l1X + 100);
self.solver.add_constraint(_r1Y == _l1Y + 100);
self.solver.solve();
NSTrackingAreaOptions options = (NSTrackingActiveAlways | NSTrackingInVisibleRect |
NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
self.trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:options owner:self userInfo:nil];
[self addTrackingArea:self.trackingArea];
}
-(void)updateTrackingAreas
{
[self removeTrackingArea:self.trackingArea];
NSTrackingAreaOptions options = (NSTrackingActiveAlways | NSTrackingInVisibleRect |
NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
self.trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:options owner:self userInfo:nil];
[self addTrackingArea:self.trackingArea];
}
-(void)mouseMoved:(NSEvent *)theEvent
{
CGPoint curPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
self.solver.suggest(_l1X, curPoint.x);
self.solver.suggest(_l1Y, curPoint.y);
self.solver.solve();
[self setNeedsDisplay:YES];
}
@end
I have attached a screenshot that shows what is the result. As you can see, it seems to work for the first time (l1X and l1Y is 0 at the beginning which is the lower left corner in Mac OS X), but r1X and r1Y never change when moving the mouse.
What's wrong with my code?
This code
static rhea::simplex_solver solver;
static rhea::variable testValue (10);
solver.add_constraints({
testValue >= 200
});
solver.on_variable_change = [self](const rhea::variable& v, rhea::simplex_solver& s) {
if(v.is(testValue)) {
NSLog(@"Blue top %.0f", v.value());
}
};
solver.on_resolve = [self](rhea::simplex_solver &solver) {
NSLog(@"Solved. Blue top: %.0f", testValue.value());
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
solver.suggest({ { testValue, 500 } });
});
Produces
2014-09-22 17:16:38.879 IVYLayoutEngine[51431:4937714] Blue top 200
2014-09-22 17:16:38.879 IVYLayoutEngine[51431:4937714] Solved. Blue top: 200
2014-09-22 17:16:38.880 IVYLayoutEngine[51431:4937714] Blue top 500
2014-09-22 17:16:38.880 IVYLayoutEngine[51431:4937714] Solved. Blue top: 500
2014-09-22 17:16:38.880 IVYLayoutEngine[51431:4937714] Blue top 200
2014-09-22 17:16:38.880 IVYLayoutEngine[51431:4937714] Solved. Blue top: 200
I would expect it to produce
2014-09-22 17:16:38.879 IVYLayoutEngine[51431:4937714] Blue top 200
2014-09-22 17:16:38.879 IVYLayoutEngine[51431:4937714] Solved. Blue top: 200
2014-09-22 17:16:38.880 IVYLayoutEngine[51431:4937714] Blue top 500
2014-09-22 17:16:38.880 IVYLayoutEngine[51431:4937714] Solved. Blue top: 500
I am trying out the 0.3 branch. When it is ready I recommend publishing it as version 1.0.0 as there are several breaking changes. Following Semantic Versioning is always a good thing. It suggests bumping the major number (major.minor.patch) whenever making a breaking change.
I would like to publish a podspec named "1.0.0-beta" pointing to commit cfd33f3
.
It seems like nonlinear expressions is handled automagically. I don't know if this is by design since the java version throws a "nonlinear expression exception".
// This is a nonlinear expression and should not be supported
BOOST_AUTO_TEST_CASE(variable_is_denominator)
{
variable x(0), y(0);
simplex_solver solver;
BOOST_CHECK_EQUAL(x.value(), 0);
BOOST_CHECK_EQUAL(y.value(), 0);
solver.add_constraint(y == 10);
BOOST_CHECK_EQUAL(x.value(), 0);
BOOST_CHECK_EQUAL(y.value(), 10);
solver.add_constraint(x == 5 / y);
BOOST_CHECK_EQUAL(x.value(), 0.5);
BOOST_CHECK_EQUAL(y.value(), 10);
}
// This is a linear expression and should be supported
BOOST_AUTO_TEST_CASE(variable_is_numerator)
{
variable x(0), y(0);
simplex_solver solver;
BOOST_CHECK_EQUAL(x.value(), 0);
BOOST_CHECK_EQUAL(y.value(), 0);
solver.add_constraint(y == 10);
BOOST_CHECK_EQUAL(x.value(), 0);
BOOST_CHECK_EQUAL(y.value(), 10);
solver.add_constraint(x == y / 5);
BOOST_CHECK_EQUAL(x.value(), 2);
BOOST_CHECK_EQUAL(y.value(), 10);
}
Result
Running 41 test cases...
/unit_tests.cpp:777: error in "variable_is_denominator": check x.value() == 0.5 failed [2 != 0.5]
*** 1 failure detected in test suite "rhea"
This is the Rhea implementation
linear_expression& linear_expression::operator/=(const linear_expression& x)
{
if (is_constant())
return *this = x / constant();
if (!x.is_constant())
throw nonlinear_expression();
return operator/=(x.constant());
}
This is the java implementation
public final Expression divide(double x)
throws NonlinearExpression
{
if (Util.approx(x, 0.0))
{
throw new NonlinearExpression();
}
return times(1.0 / x);
}
I'm not sure what's wrong. I'm not that familiar with std and c++. In the podspec I'm stating
s.ios.deployment_target = '6.0'
s.osx.deployment_target = '10.8'
s.library = 'c++'
s.pod_target_xcconfig = {
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++11',
'CLANG_CXX_LIBRARY' => 'libc++'
}
Does those settings seem right to you?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.