Skip to content

Commit

Permalink
Merge pull request #245 from princeton-vl/f_bugfix_solver
Browse files Browse the repository at this point in the history
Fix ignored / reversed constraints, tune constraints, fix docs & manage_jobs bugs
  • Loading branch information
araistrick authored Aug 23, 2024
2 parents 04fe4d3 + 805bc8b commit ef50641
Show file tree
Hide file tree
Showing 30 changed files with 342 additions and 280 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:

- name: Install infinigen & dependencies
run: |
pip install .[dev]
pip install ".[dev]"
- name: Test with pytest
run: |
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ RUN conda init bash && \
. ~/.bashrc && \
conda create --name infinigen python=3.10 -y && \
conda activate infinigen && \
pip install -e .[dev]
pip install -e ".[dev]"
8 changes: 8 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,11 @@ v1.8.0
- Use constraint language to configure room solving
- Add pillars, vertically split wall materials

v1.8.1
- Fix bug causing hard constraints on scalar inequalities (e.g distance > x) to be ignored
- Fix bug causing livingroom sofa alignment to be incorrect
- Fix bias in camera trajectory starting direction
- Improve visual quality of home.py via constraint tweaks and new generate_indoors stages
- Fix silent output from upload stage, remove export from upload
- Reduce solving time spent on small objects

2 changes: 1 addition & 1 deletion docs/ExportingToSimulators.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This documentation details how to run a robotics simulation in an exported Infin
First, create and export a scene with the commands below:

```bash
python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g overhead_singleroom.gin -p compose_indoors.terrain_enabled=False compose_indoors.solve_max_rooms=1
python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g overhead_singleroom.gin -p compose_indoors.terrain_enabled=False restrict_solving.solve_max_rooms=1
```

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/GroundTruthAnnotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
To run the visualization scripts below you will need to install extra dependencies

```bash
pip install .[vis]
pip install ".[vis]"
```

## Default Annotations from Blender
Expand Down
2 changes: 1 addition & 1 deletion docs/HelloRoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_fo
python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g no_objects.gin overhead.gin -p compose_indoors.terrain_enabled=False

# Single random room with objects, overhead view (~11min. runtime CPU only):
python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin overhead.gin singleroom.gin -p compose_indoors.terrain_enabled=False compose_indoors.overhead_cam_enabled=True compose_indoors.solve_max_rooms=1 compose_indoors.invisible_room_ceilings_enabled=True compose_indoors.restrict_single_supported_roomtype=True
python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin overhead.gin singleroom.gin -p compose_indoors.terrain_enabled=False compose_indoors.overhead_cam_enabled=True restrict_solving.solve_max_rooms=1 compose_indoors.invisible_room_ceilings_enabled=True compose_indoors.restrict_single_supported_roomtype=True

# Whole apartment with objects, overhead view:
python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin overhead.gin studio.gin -p compose_indoors.terrain_enabled=False
Expand Down
2 changes: 1 addition & 1 deletion docs/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Then, install the infinigen package using one of the options below:
INFINIGEN_MINIMAL_INSTALL=True pip install -e .

# Full install (Terrain & OpenGL-GT enabled, needed for Infinigen-Nature HelloWorld)
pip install -e .[terrain,vis]
pip install -e ".[terrain,vis]"

# Developer install (includes pytest, ruff, other recommended dev tools)
pip install -e ".[dev,terrain,vis]"
Expand Down
2 changes: 1 addition & 1 deletion infinigen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import logging
from pathlib import Path

__version__ = "1.8.0"
__version__ = "1.8.1"


def repo_root():
Expand Down
1 change: 1 addition & 0 deletions infinigen/core/constraints/constraint_language/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ScalarExpression,
ScalarOperatorExpression,
constant,
debugprint,
hinge,
max_expr,
min_expr,
Expand Down
7 changes: 7 additions & 0 deletions infinigen/core/constraints/constraint_language/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,10 @@ class hinge(ScalarExpression):
val: ScalarExpression
low: float
high: float


@Expression.register_postfix_func
@nodedataclass()
class debugprint(ScalarExpression, BoolExpression):
val: Expression
msg: str
4 changes: 0 additions & 4 deletions infinigen/core/constraints/constraint_language/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,6 @@ class StableAgainst(GeometryRelation):
# typical use is chair-against-table relation
check_z: bool = True

# rev_normal: if True, align the normals so they face the SAME direction, rather than two planes facing eachother.
# typical use is for sink embedded in countertop
rev_normal: bool = False

__repr__ = no_frozenset_repr


Expand Down
76 changes: 50 additions & 26 deletions infinigen/core/constraints/evaluator/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@

logger = logging.getLogger(__name__)

SPECIAL_CASE_NODES = [cl.ForAll, cl.SumOver, cl.MeanOver, cl.item, cl.Problem, cl.scene]
SPECIAL_CASE_NODES = [
cl.ForAll,
cl.SumOver,
cl.MeanOver,
cl.item,
cl.Problem,
cl.scene,
cl.debugprint,
]

gather_funcs = {
cl.ForAll: all,
Expand Down Expand Up @@ -46,7 +54,7 @@ def _compute_node_val(node: cl.Node, state: State, memo: dict):
memo_sub[var] = {o}
results.append(evaluate_node(pred, state, memo=memo_sub))

logger.debug(f"{node.__class__.__name__} had {len(results)=}")
# slogger.debug(f"{node.__class__.__name__} had {len(results)=}")

return gather_funcs[node.__class__](results)
case cl.item():
Expand All @@ -66,6 +74,15 @@ def _compute_node_val(node: cl.Node, state: State, memo: dict):
raise TypeError(
f"evaluate_node is invalid for {node}, please use evaluate_problem"
)
case cl.debugprint(val, msg):
res = evaluate_node(val, state, memo)
var_assignments = [
v
for k, v in memo.items()
if isinstance(k, str) and k.startswith("var_")
]
print(f"cl.debugprint {msg}: {res} {var_assignments}")
return res
case _:
raise NotImplementedError(
f"Couldnt compute value for {type(node)}, please add it to "
Expand All @@ -80,38 +97,45 @@ def relevant(node: cl.Node, filter: r.Domain | None) -> bool:
return True

if not isinstance(node, cl.Node):
raise ValueError(f"{node=}")
raise TypeError(f"{node=}")

match node:
case cl.ObjectSetExpression():
d = r.constraint_domain(node, finalize_variables=True)
assert r.domain_finalized(
d
), f"{relevant.__name__} encountered unfinalized {d=}"
if not r.domain_finalized(d):
raise RuntimeError(f"{relevant.__name__} encountered unfinalized {d=}")
res = d.intersects(filter, require_satisfies_right=True)
logger.debug(f"{relevant.__name__} got {res=} for {d=}\n {filter=}")
return res
case _:
return any(relevant(c, filter) for _, c in node.children())


def _viol_count_binop(node: cl.BoolOperatorExpression, lhs, rhs) -> int:
if not isinstance(lhs, int) or not isinstance(rhs, int):
satisfied = node.func(lhs, rhs)
return 1 if not satisfied else 0

def _viol_count_binop_integer(
node: cl.BoolOperatorExpression, lhs: int, rhs: int
) -> int:
match node.func:
case operator.ge:
return max(0, rhs - lhs)
case operator.le:
return max(0, lhs - rhs)
case operator.gt:
return max(0, rhs - lhs + 1)
case operator.lt:
return max(0, lhs - rhs + 1)
case operator.ge: # lhs >= rhs
err = rhs - lhs
case operator.le: # lhs <= rhs
err = lhs - rhs
case operator.gt: # lhs > rhs
err = rhs - lhs + 1
case operator.lt: # lhs < rhs
err = lhs - rhs + 1
case _:
raise ValueError(f"Unhandled {node.func=}")

return max(err, 0)


def _viol_count_binop(node: cl.BoolOperatorExpression, lhs, rhs) -> float:
if isinstance(lhs, int) and isinstance(rhs, int):
return _viol_count_binop_integer(node, lhs, rhs)
else:
satisfied = node.func(lhs, rhs)
return 1 if not satisfied else 0


def viol_count(node: cl.Node, state: State, memo: dict, filter: r.Domain = None):
match node:
Expand Down Expand Up @@ -142,13 +166,11 @@ def viol_count(node: cl.Node, state: State, memo: dict, filter: r.Domain = None)
memo_sub[var] = {o}
viol += viol_count(pred, state, memo_sub, filter)
res = viol
case (
cl.BoolOperatorExpression(operator.ge, [lhs, rhs])
| cl.BoolOperatorExpression(operator.le, [rhs, lhs])
| cl.BoolOperatorExpression(operator.gt, [rhs, lhs])
| cl.BoolOperatorExpression(operator.lt, [rhs, lhs])
case cl.BoolOperatorExpression(
operator.ge | operator.le | operator.gt | operator.lt, [lhs, rhs]
):
if relevant(lhs, filter) or relevant(rhs, filter):
either_relevant = relevant(lhs, filter) or relevant(rhs, filter)
if either_relevant:
l_res = evaluate_node(lhs, state, memo)
r_res = evaluate_node(rhs, state, memo)
res = _viol_count_binop(node, l_res, r_res)
Expand Down Expand Up @@ -246,6 +268,8 @@ def evaluate_problem(
for name, node in problem.constraints.items():
logger.debug(f"Evaluating constraint {name=}")
violated[name] = viol_count(node, state, memo, filter=filter)
logger.debug(f"Evaluator found {violated[name]} violations for {name=}")

if violated[name]:
logger.debug(f"Evaluator found {violated[name]} violations for {name=}")

return EvalResult(loss_vals=scores, violations=violated)
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ def preprocess_collision_query_cases(a, b, a_tags, b_tags):
if isinstance(b, list):
b = set(b)

if len(a) == 1:
if a is not None and len(a) == 1:
a = a.pop()
if len(b) == 1:
if b is not None and len(b) == 1:
b = b.pop()

# eliminate symmetrical cases
Expand Down Expand Up @@ -196,8 +196,8 @@ class DistanceResult:

def min_dist(
scene: Scene,
a: Union[str, list[str]],
b: Union[str, list[str]] = None,
a: str | list[str] | None,
b: str | list[str] | None = None,
a_tags: set = None,
b_tags: set = None,
bvh_cache: dict = None,
Expand Down Expand Up @@ -740,6 +740,7 @@ def angle_alignment_cost(
return angle_alignment_cost_base(state, a, b, visualize)


@gin.configurable
def focus_score(
state: state_def.State, a: Union[str, list[str]], b: str, visualize=False
):
Expand Down
2 changes: 1 addition & 1 deletion infinigen/core/constraints/example_solver/annealing.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(
checkpoint_best=False,
output_folder=None,
visualize=False,
print_report_freq=10,
print_report_freq=1,
print_breakdown_freq=0,
) -> None:
self.initial_temp = initial_temp
Expand Down
6 changes: 3 additions & 3 deletions infinigen/core/constraints/example_solver/propose_discrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ def propose_addition_bound_gen(
rd for rd in prop_dom.relations if not isinstance(rd[0], cl.NegatedRelation)
]

all_assignments = propose_relations.find_assignments(curr, search_rels)

i = None
for i, assignments in enumerate(
propose_relations.find_assignments(curr, search_rels)
):
for i, assignments in enumerate(all_assignments):
logger.debug("Found assignments %d %s %s", i, len(assignments), assignments)

yield moves.Addition(
Expand Down
17 changes: 9 additions & 8 deletions infinigen/core/constraints/example_solver/solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ class Solver:
def __init__(
self,
output_folder: Path,
restrict_moves: list = None,
addition_weight_scalar: float = 1.0,
):
"""Initialize the solver
Expand All @@ -84,10 +82,6 @@ def __init__(
self.state: State = None
self.dimensions = None

self.moves = self._configure_move_weights(
restrict_moves, addition_weight_scalar=addition_weight_scalar
)

def _configure_move_weights(self, restrict_moves, addition_weight_scalar=1.0):
schedules = {
"addition": (
Expand Down Expand Up @@ -127,11 +121,12 @@ def _configure_move_weights(self, restrict_moves, addition_weight_scalar=1.0):
@gin.configurable
def choose_move_type(
self,
moves: dict[str, tuple],
it: int,
max_it: int,
):
t = it / max_it
names, confs = zip(*self.moves.items())
names, confs = zip(*moves.items())
funcs, scheds = zip(*confs)
weights = np.array([s if isinstance(s, (float, int)) else s(t) for s in scheds])
return np.random.choice(funcs, p=weights / weights.sum())
Expand All @@ -150,7 +145,13 @@ def solve_objects(
desc: str,
abort_unsatisfied: bool = False,
print_bounds: bool = False,
restrict_moves: list[str] = None,
addition_weight_scalar: float = 1.0,
):
moves = self._configure_move_weights(
restrict_moves, addition_weight_scalar=addition_weight_scalar
)

filter_domain = copy.deepcopy(filter_domain)

desc_full = (desc, *var_assignments.values())
Expand Down Expand Up @@ -186,7 +187,7 @@ def solve_objects(
self.optim.reset(max_iters=n_steps)
ra = trange(n_steps) if self.optim.print_report_freq == 0 else range(n_steps)
for j in ra:
move_gen = self.choose_move_type(j, n_steps)
move_gen = self.choose_move_type(moves, j, n_steps)
self.optim.step(consgraph, self.state, move_gen, filter_domain)
self.optim.save_stats(self.output_folder / f"optim_{desc}.csv")

Expand Down
2 changes: 1 addition & 1 deletion infinigen/core/placement/animation_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def __init__(
yaw_sampler=("uniform", -20, 20),
step_range=("clip_gaussian", 3, 5, 0.5, 10),
rot_vars=(5, 0, 5),
motion_dir_zoff=("clip_gaussian", 0, 90, 0, 180),
motion_dir_zoff=("uniform", 0, 360),
force_single_keyframe=False,
):
self.speed = random_general(speed)
Expand Down
4 changes: 3 additions & 1 deletion infinigen/core/util/blender.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
from math import prod
from pathlib import Path

import bmesh
# ruff: noqa: I001
# must import bpy before bmesh
import bpy
import bmesh
import mathutils
import numpy as np
import trimesh
Expand Down
1 change: 0 additions & 1 deletion infinigen/datagen/configs/upload.gin
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
iterate_scene_tasks.finalize_tasks = [
{'name': "export", 'func': @queue_export},
{'name': "upload", 'func': @queue_upload}
]

Expand Down
Loading

0 comments on commit ef50641

Please sign in to comment.