diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 3973f9593..6d962e8fd 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -43,7 +43,7 @@ jobs: - name: Install infinigen & dependencies run: | - pip install .[dev] + pip install ".[dev]" - name: Test with pytest run: | diff --git a/Dockerfile b/Dockerfile index 159fbf5ab..37cca12c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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]" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a316c446c..f65c40263 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -98,3 +98,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 + diff --git a/docs/ExportingToSimulators.md b/docs/ExportingToSimulators.md index fe8fdadf0..951a75f56 100644 --- a/docs/ExportingToSimulators.md +++ b/docs/ExportingToSimulators.md @@ -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 diff --git a/docs/GroundTruthAnnotations.md b/docs/GroundTruthAnnotations.md index 7e69f074a..2f2485ae0 100644 --- a/docs/GroundTruthAnnotations.md +++ b/docs/GroundTruthAnnotations.md @@ -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 diff --git a/docs/HelloRoom.md b/docs/HelloRoom.md index c7628fe38..ba12c4bcf 100644 --- a/docs/HelloRoom.md +++ b/docs/HelloRoom.md @@ -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 diff --git a/docs/Installation.md b/docs/Installation.md index 867bd4a73..1fbad7bf8 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -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]" diff --git a/infinigen/__init__.py b/infinigen/__init__.py index ab93dd97b..6802bdf7d 100644 --- a/infinigen/__init__.py +++ b/infinigen/__init__.py @@ -6,7 +6,7 @@ import logging from pathlib import Path -__version__ = "1.8.0" +__version__ = "1.8.1" def repo_root(): diff --git a/infinigen/core/constraints/constraint_language/__init__.py b/infinigen/core/constraints/constraint_language/__init__.py index 3c3f7ff75..464fd0a15 100644 --- a/infinigen/core/constraints/constraint_language/__init__.py +++ b/infinigen/core/constraints/constraint_language/__init__.py @@ -12,6 +12,7 @@ ScalarExpression, ScalarOperatorExpression, constant, + debugprint, hinge, max_expr, min_expr, diff --git a/infinigen/core/constraints/constraint_language/expression.py b/infinigen/core/constraints/constraint_language/expression.py index 3aa219569..af049f502 100644 --- a/infinigen/core/constraints/constraint_language/expression.py +++ b/infinigen/core/constraints/constraint_language/expression.py @@ -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 diff --git a/infinigen/core/constraints/constraint_language/relations.py b/infinigen/core/constraints/constraint_language/relations.py index eaf22e52d..636f9e969 100644 --- a/infinigen/core/constraints/constraint_language/relations.py +++ b/infinigen/core/constraints/constraint_language/relations.py @@ -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 diff --git a/infinigen/core/constraints/evaluator/evaluate.py b/infinigen/core/constraints/evaluator/evaluate.py index 294314fe2..dd792bb51 100644 --- a/infinigen/core/constraints/evaluator/evaluate.py +++ b/infinigen/core/constraints/evaluator/evaluate.py @@ -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, @@ -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(): @@ -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 " @@ -80,14 +97,13 @@ 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 @@ -95,23 +111,31 @@ def relevant(node: cl.Node, filter: r.Domain | None) -> bool: 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: @@ -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) @@ -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) diff --git a/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py b/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py index 0d180b369..aaf03f759 100644 --- a/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py +++ b/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py @@ -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 @@ -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, @@ -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 ): diff --git a/infinigen/core/constraints/example_solver/annealing.py b/infinigen/core/constraints/example_solver/annealing.py index 7656b7a33..71fc4ff93 100644 --- a/infinigen/core/constraints/example_solver/annealing.py +++ b/infinigen/core/constraints/example_solver/annealing.py @@ -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 diff --git a/infinigen/core/constraints/example_solver/propose_discrete.py b/infinigen/core/constraints/example_solver/propose_discrete.py index a09cd0bc9..2b107d91b 100644 --- a/infinigen/core/constraints/example_solver/propose_discrete.py +++ b/infinigen/core/constraints/example_solver/propose_discrete.py @@ -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( diff --git a/infinigen/core/constraints/example_solver/solve.py b/infinigen/core/constraints/example_solver/solve.py index e4caf8c0e..117ed6e37 100644 --- a/infinigen/core/constraints/example_solver/solve.py +++ b/infinigen/core/constraints/example_solver/solve.py @@ -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 @@ -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": ( @@ -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()) @@ -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()) @@ -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") diff --git a/infinigen/core/placement/animation_policy.py b/infinigen/core/placement/animation_policy.py index d9257a9c4..ec3ef7d65 100644 --- a/infinigen/core/placement/animation_policy.py +++ b/infinigen/core/placement/animation_policy.py @@ -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) diff --git a/infinigen/core/util/blender.py b/infinigen/core/util/blender.py index 3ac9e917a..41b9ad4e4 100644 --- a/infinigen/core/util/blender.py +++ b/infinigen/core/util/blender.py @@ -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 diff --git a/infinigen/datagen/configs/upload.gin b/infinigen/datagen/configs/upload.gin index 4c8e7b78e..d0d71d163 100644 --- a/infinigen/datagen/configs/upload.gin +++ b/infinigen/datagen/configs/upload.gin @@ -1,5 +1,4 @@ iterate_scene_tasks.finalize_tasks = [ - {'name': "export", 'func': @queue_export}, {'name': "upload", 'func': @queue_upload} ] diff --git a/infinigen/datagen/job_funcs.py b/infinigen/datagen/job_funcs.py index b01c05511..7301d58a1 100644 --- a/infinigen/datagen/job_funcs.py +++ b/infinigen/datagen/job_funcs.py @@ -11,7 +11,6 @@ import logging import re import sys -from functools import partial from pathlib import Path from shutil import copytree from uuid import uuid4 @@ -19,15 +18,19 @@ import gin import infinigen -from infinigen.datagen.util import upload_util from infinigen.datagen.util.show_gpu_table import nodes_with_gpus -from infinigen.datagen.util.upload_util import upload_job_folder +from infinigen.datagen.util.upload_util import get_commit_hash from infinigen.tools.suffixes import get_suffix from . import states logger = logging.getLogger(__name__) +UPLOAD_UTIL_PATH = ( + infinigen.repo_root() / "infinigen" / "datagen" / "util" / "upload_util.py" +) +assert UPLOAD_UTIL_PATH.exists(), f"{UPLOAD_UTIL_PATH=} does not exist" + @gin.configurable def get_cmd( @@ -82,8 +85,19 @@ def queue_upload( seed=None, **kwargs, ): - func = partial(upload_job_folder, dir_prefix_len=dir_prefix_len, method=method) - res = submit_cmd((func, folder, taskname), folder, name, **kwargs) + modulepath = str( + UPLOAD_UTIL_PATH.with_suffix("").relative_to(infinigen.repo_root()) + ).replace("/", ".") + + cmd = ( + f"{sys.executable} -m {modulepath} " + "--parent_folder " + str(folder) + " " + "--task_uniqname " + taskname + " " + f"--dir_prefix_len {dir_prefix_len} " + f"--method {method}" + ).split() + + res = submit_cmd(cmd, folder, name, **kwargs) return res, None @@ -157,8 +171,7 @@ def queue_coarse( + overrides ) - commit = upload_util.get_commit_hash() - + commit = get_commit_hash() with (folder / "run_pipeline.sh").open("w") as f: f.write(f"# git checkout {commit}\n\n") f.write(f"{' '.join(' '.join(cmd).split())}\n\n") @@ -496,19 +509,21 @@ def queue_opengl( return states.JOB_OBJ_SUCCEEDED, None output_suffix = get_suffix(output_indices) - - input_folder = Path(folder)/f'savemesh{output_suffix}' # OUTPUT SUFFIX IS CORRECT HERE. I know its weird. But input suffix really means 'prev tier of the pipeline + + input_folder = ( + Path(folder) / f"savemesh{output_suffix}" + ) # OUTPUT SUFFIX IS CORRECT HERE. I know its weird. But input suffix really means 'prev tier of the pipeline # dst_output_indices = dict(output_indices) - start_frame, end_frame = output_indices['frame'], output_indices['last_cam_frame'] + start_frame, end_frame = output_indices["frame"], output_indices["last_cam_frame"] key = "execute_tasks.point_trajectory_src_frame=" point_trajectory_src_frame = None for item in overrides: if item.startswith(key): - point_trajectory_src_frame = int(item[len(key):]) - assert(point_trajectory_src_frame is not None) + point_trajectory_src_frame = int(item[len(key) :]) + assert point_trajectory_src_frame is not None - if (gt_testing): + if gt_testing: copy_folder = Path(folder) / f"frames{output_suffix}" output_folder = Path(folder) / f"opengl_frames{output_suffix}" copytree(copy_folder, output_folder, dirs_exist_ok=True) @@ -516,21 +531,22 @@ def queue_opengl( output_folder = Path(folder) / f"frames{output_suffix}" output_folder.mkdir(exist_ok=True) - assert isinstance(overrides, list) and ("\n" not in ' '.join(overrides)) + assert isinstance(overrides, list) and ("\n" not in " ".join(overrides)) tmp_script = Path(folder) / "tmp" / f"opengl_{uuid4().hex}.sh" tmp_script.parent.mkdir(exist_ok=True) - with tmp_script.open('w') as f: - + with tmp_script.open("w") as f: lines = [ "set -e", - f''' + f""" if [ ! -d "{str(input_folder)}" ]; then exit 1 fi - ''', + """, ] - lines.append(f"{sys.executable} {infinigen.repo_root()/'infinigen/tools/process_static_meshes.py'} {input_folder} {point_trajectory_src_frame}") + lines.append( + f"{sys.executable} {infinigen.repo_root()/'infinigen/tools/process_static_meshes.py'} {input_folder} {point_trajectory_src_frame}" + ) lines += [ f"{process_mesh_path} -in {input_folder} -dst_in {input_folder} " f"--frame {frame_idx} --dst_frame {frame_idx+1} -out {output_folder} " @@ -553,9 +569,13 @@ def queue_opengl( f"{process_mesh_path} -in {input_folder} -dst_in {input_folder} " f"--frame {point_trajectory_src_frame} --dst_frame {point_trajectory_src_frame} --depth_only 1 -out {output_folder} " ] - - lines.append(f"{sys.executable} {infinigen.repo_root()/'infinigen/tools/compress_masks.py'} {output_folder}") - lines.append(f"{sys.executable} {infinigen.repo_root()/'infinigen/tools/compute_occlusion_masks.py'} {output_folder} {point_trajectory_src_frame}") + + lines.append( + f"{sys.executable} {infinigen.repo_root()/'infinigen/tools/compress_masks.py'} {output_folder}" + ) + lines.append( + f"{sys.executable} {infinigen.repo_root()/'infinigen/tools/compute_occlusion_masks.py'} {output_folder} {point_trajectory_src_frame}" + ) lines.append( f"{sys.executable} {infinigen.repo_root()/'infinigen/tools/compress_masks.py'} {output_folder}" diff --git a/infinigen/datagen/util/upload_util.py b/infinigen/datagen/util/upload_util.py index 2bce4d02f..8941e758b 100644 --- a/infinigen/datagen/util/upload_util.py +++ b/infinigen/datagen/util/upload_util.py @@ -16,8 +16,6 @@ from datetime import datetime from pathlib import Path -from infinigen.core.util.logging import Suppress - from . import smb_client RCLONE_PREFIX_ENVVAR = "INFINIGEN_RCLONE_PREFIX" @@ -128,9 +126,8 @@ def get_commit_hash(): if git is None: return None try: - with Suppress(): - cmd = f"{git} rev-parse HEAD" - return subprocess.check_output(cmd.split()).decode().strip() + cmd = f"{git} rev-parse HEAD" + return subprocess.check_output(cmd.split()).decode().strip() except subprocess.CalledProcessError: return None @@ -194,9 +191,8 @@ def get_upload_destfolder(job_folder): # DO NOT make gin.configurable # this function gets submitted via pickle in some settings, and gin args are not preserved def upload_job_folder( - parent_folder, task_uniqname, dir_prefix_len=0, method="smbclient" + parent_folder: Path, task_uniqname: str, dir_prefix_len=0, method="smbclient" ): - parent_folder = Path(parent_folder) seed = parent_folder.name print(f"Performing cleanup on {parent_folder}") @@ -225,6 +221,7 @@ def upload_job_folder( for f in upload_paths: if f is None: continue + print(f"Uploading {f}") upload_func(f, upload_dest_folder) f.unlink() @@ -233,8 +230,15 @@ def upload_job_folder( if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("parent_folder", type=Path) - parser.add_argument("task_uniqname", type=str) + parser.add_argument("--parent_folder", type=Path) + parser.add_argument("--task_uniqname", type=str) + parser.add_argument("--dir_prefix_len", type=int, default=0) + parser.add_argument("--method", type=str, default="smbclient") args = parser.parse_args() - upload_job_folder(args.parent_folder, args.task_uniqname) + upload_job_folder( + parent_folder=args.parent_folder, + task_uniqname=args.task_uniqname, + dir_prefix_len=args.dir_prefix_len, + method=args.method, + ) diff --git a/infinigen_examples/configs_indoor/base_indoors.gin b/infinigen_examples/configs_indoor/base_indoors.gin index a1013a52a..9894140a8 100644 --- a/infinigen_examples/configs_indoor/base_indoors.gin +++ b/infinigen_examples/configs_indoor/base_indoors.gin @@ -2,9 +2,9 @@ include 'infinigen_examples/configs_nature/base.gin' include 'infinigen_examples/configs_nature/performance/fast_terrain_assets.gin' # overriden in fast_solve.gin if present -compose_indoors.solve_steps_large = 500 +compose_indoors.solve_steps_large = 300 compose_indoors.solve_steps_medium = 200 -compose_indoors.solve_steps_small = 300 +compose_indoors.solve_steps_small = 50 SimulatedAnnealingSolver.initial_temp = 3 SimulatedAnnealingSolver.final_temp = 0.001 diff --git a/infinigen_examples/configs_indoor/fast_solve.gin b/infinigen_examples/configs_indoor/fast_solve.gin index 1439acc10..d712c7750 100644 --- a/infinigen_examples/configs_indoor/fast_solve.gin +++ b/infinigen_examples/configs_indoor/fast_solve.gin @@ -1,14 +1,14 @@ -FloorPlanSolver.n_divide_trials = 10 -FloorPlanSolver.iters_mult = 10 +FloorPlanSolver.n_divide_trials = 25 +FloorPlanSolver.iters_mult = 25 RoomConstants.n_stories = 1 GraphMaker.fast=True home_room_constraints.fast = True -Solver.addition_weight_scalar = 3.0 +solve_objects.addition_weight_scalar = 3.0 -compose_indoors.solve_steps_large = 150 +compose_indoors.solve_steps_large = 100 compose_indoors.solve_steps_medium = 40 -compose_indoors.solve_steps_small = 10 +compose_indoors.solve_steps_small = 5 compose_indoors.terrain_enabled = False diff --git a/infinigen_examples/constraints/home.py b/infinigen_examples/constraints/home.py index e2858993e..f03f51038 100644 --- a/infinigen_examples/constraints/home.py +++ b/infinigen_examples/constraints/home.py @@ -39,11 +39,11 @@ def sample_home_constraint_params(): # how many objects in each shelving per unit of volume obj_interior_obj_pct=uniform(0.5, 1), # uniform(0.6, 0.9), # what pct of top surface of storage furniture should be filled with objects? e.g pct of top surface of shelf - obj_on_storage_pct=uniform(0.1, 0.9), + obj_on_storage_pct=uniform(0.5, 1.0), # what pct of top surface of NON-STORAGE objects should be filled with objects? e.g pct of countertop/diningtable covered in stuff - obj_on_nonstorage_pct=uniform(0.1, 0.6), + obj_on_nonstorage_pct=uniform(0.2, 1.0), # meters squared of wall art per approx meters squared of FLOOR area. TODO cant measure wall area currently. - painting_area_per_room_area=uniform(20, 60) / 40, + painting_area_per_room_area=uniform(40, 100) / 40, # rare objects wont even be added to the constraint graph in most homes has_tv=uniform() < 0.5, has_aquarium_tank=uniform() < 0.15, @@ -512,7 +512,9 @@ def home_furniture_constraints(): furniture = obj[Semantics.Furniture].related_to(rooms, cu.on_floor) wallfurn = furniture.related_to(rooms, cu.against_wall) - storage = wallfurn[Semantics.Storage] + + storage = furniture[Semantics.Storage] + storage_freestanding = storage.related_to(rooms, cu.against_wall) params = sample_home_constraint_params() @@ -593,7 +595,7 @@ def top_fullness_pct(f): ) constraints["storage"] = rooms.all( - lambda r: (storage.related_to(r).count().in_range(1, 7)) + lambda r: (storage_freestanding.related_to(r).count().in_range(1, 7)) ) score_terms["storage"] = rooms.mean( lambda r: ( @@ -628,7 +630,9 @@ def top_fullness_pct(f): mirror = walldec[wall_decorations.MirrorFactory] rugs = obj[elements.RugFactory].related_to(rooms, cu.on_floor) - constraints["rugs"] = rooms.all(lambda r: (rugs.related_to(r).distance(rugs) >= 1)) + constraints["rugs"] = rooms.all( + lambda r: (cl.min_distance_internal(rugs.related_to(r)) >= 1) + ) score_terms["rugs"] = rooms.all( lambda r: (cl.center_stable_surface_dist(rugs.related_to(r)).minimize(weight=1)) @@ -641,11 +645,12 @@ def vertical_diff(o, r): lambda r: ( wall_art.related_to(r).count().in_range(0, 6) * mirror.related_to(r).count().in_range(0, 1) - * walldec.related_to(r).all(lambda t: t.distance(r, cu.floortags) > 0.6) - # walldec.all(lambda t: ( - # (vertical_diff(t, r).abs() < 1.5) * - # (t.distance(cutters) > 0.1) - # )) + * walldec.related_to(r).all(lambda t: (t.distance(r, cu.floortags) > 0.6)) + * walldec.all( + lambda t: ( + (vertical_diff(t, r).abs() < 1.5) * (t.distance(cutters) > 0.1) + ) + ) ) ) score_terms["wall_decorations"] = rooms.mean( @@ -698,24 +703,13 @@ def vertical_diff(o, r): ) # endregion - # region SIDETABLE - sidetable = furniture[Semantics.SideTable].related_to(furniture, cu.side_by_side) - - score_terms["sidetable"] = rooms.mean( - lambda r: ( - sidetable.related_to(r).mean( - lambda t: (t.distance(r, cu.walltags).minimize(weight=1)) - ) - ) - ) - # endregion - # region DESKS desks = wallfurn[shelves.SimpleDeskFactory] deskchair = furniture[seating.OfficeChairFactory].related_to( - desks, cu.front_against + desks, cu.front_to_front ) monitors = obj[appliances.MonitorFactory] + constraints["desk"] = rooms.all( lambda r: ( desks.related_to(r).all( @@ -760,10 +754,12 @@ def vertical_diff(o, r): .related_to(rooms, cu.on_floor) .related_to(rooms, cu.against_wall) ) - # constraints['lighting'] = rooms.all(lambda r: ( - # # dont put redundant lights close to eachother (including lamps, ceiling lights, etc) - # lights.related_to(r).all(lambda l: l.distance(lights.related_to(r)) >= 2) - # )) + constraints["lighting"] = rooms.all( + lambda r: ( + # dont put redundant lights close to eachother (including lamps, ceiling lights, etc) + cl.min_distance_internal(lights.related_to(r)) >= 1 + ) + ) # endregion @@ -792,8 +788,9 @@ def vertical_diff(o, r): lambda r: ( # allow 0-2 lamps per room, placed on any sensible object lamps.related_to(storage.related_to(r)).count().in_range(0, 2) - # * lamps.related_to(sidetable.related_to(r)).count().in_range(0, 2) - # * lamps.related_to(desks.related_to(r, cu.on), cu.ontop).count().in_range(0, 1) + * lamps.related_to(desks.related_to(r, cu.on), cu.ontop) + .count() + .in_range(0, 1) * ( # pull-string lamps look extremely unnatural when too far off the ground lamps.related_to(storage.related_to(r)).all( lambda l: l.distance(r, cu.floortags).in_range(0.5, 1.5) @@ -802,19 +799,43 @@ def vertical_diff(o, r): ) ) - score_terms["lamps"] = lamps.mean( - lambda l: ( - cl.center_stable_surface_dist(l.related_to(sidetable)).minimize(weight=1) - + l.distance(lamps).maximize(weight=1) + # endregion + + # region SIDETABLES + sidetables = furniture[tables.SideTableFactory].related_to( + wallfurn, cu.leftright_leftright + ) + + constraints["sidetable_objects"] = rooms.all( + lambda r: ( + lamps.related_to(sidetables.related_to(r)).count().in_range(0, 2) + * sidetables.all( + lambda s: obj[Semantics.OfficeShelfItem].related_to(s, cu.on).count() + >= 0 + ) + ) + ) + + score_terms["sidetable"] = rooms.mean( + lambda r: ( + sidetables.related_to(r).mean( + lambda t: t.distance(r, cu.walltags).hinge(0, 0.3).minimize(weight=10) + ) + + lamps.mean( + lambda l: cl.center_stable_surface_dist( + l.related_to(sidetables) + ).minimize(weight=1) + ) ) ) + # endregion # region CLOSETS closets = rooms[Semantics.Closet].excludes(cu.room_types) constraints["closets"] = closets.all( lambda r: ( - (storage.related_to(r).count() >= 1) + (storage_freestanding.related_to(r).count() >= 1) * ceillights.related_to(r, cu.hanging).count().in_range(0, 1) * ( walldec.related_to(r).count() == 0 @@ -833,19 +854,15 @@ def vertical_diff(o, r): # region BEDROOMS bedrooms = rooms[Semantics.Bedroom].excludes(cu.room_types) - beds = wallfurn[Semantics.Bed][seating.BedFactory] + beds = wallfurn[Semantics.Bed] + constraints["bedroom"] = bedrooms.all( lambda r: ( beds.related_to(r).count().in_range(1, 2) - * ( - sidetable.related_to(r) - .related_to(beds.related_to(r), cu.leftright_leftright) - .count() - .in_range(0, 2) - ) + * sidetables.related_to(beds.related_to(r)).count().in_range(0, 2) * rugs.related_to(r).count().in_range(0, 1) * desks.related_to(r).count().in_range(0, 1) - * storage.related_to(r).count().in_range(2, 5) + * storage_freestanding.related_to(r).count().in_range(2, 5) * floor_lamps.related_to(r).count().in_range(0, 1) * storage.related_to(r).all( lambda s: ( @@ -857,13 +874,9 @@ def vertical_diff(o, r): score_terms["bedroom"] = bedrooms.mean( lambda r: ( - beds.related_to(r).count().maximize(weight=3) - + beds.related_to(r) + beds.related_to(r) .mean(lambda t: cl.distance(r, doors)) .maximize(weight=0.5) - + sidetable.related_to(r) - .mean(lambda t: t.distance(beds.related_to(r))) - .minimize(weight=3) ) ) @@ -1113,7 +1126,9 @@ def obj_on_counter(r): sofa_back_near_wall = cl.StableAgainst( cu.back, cu.walltags, margin=uniform(0.1, 0.3) ) - cl.StableAgainst(cu.side, cu.walltags, margin=uniform(0.1, 0.3)) + sofa_side_near_wall = cl.StableAgainst( + cu.side, cu.walltags, margin=uniform(0.1, 0.3) + ) def freestanding(o, r): return o.related_to(r).related_to(r, -sofa_back_near_wall) @@ -1121,11 +1136,12 @@ def freestanding(o, r): constraints["sofa"] = livingrooms.all( lambda r: ( # sofas.related_to(r).count().in_range(2, 3) - sofas.related_to(r, sofa_back_near_wall).count().in_range(2, 4) - # * sofas.related_to(r, sofa_side_near_wall).count().in_range(0, 1) + sofas.related_to(r, sofa_back_near_wall).count().in_range(0, 4) + * sofas.related_to(r, sofa_side_near_wall).count().in_range(0, 1) + * sofas.related_to(r, cu.on_floor).count().in_range(0, 1) * freestanding(sofas, r).all( lambda t: ( # frustrum infront of freestanding sofa must directly contain tvstand - cl.accessibility_cost(t, tvstands.related_to(r), dist=3) > 0.7 + cl.accessibility_cost(t, tvstands.related_to(r), dist=3) > 0.4 ) ) * sofas.all( @@ -1136,10 +1152,11 @@ def freestanding(o, r): * cl.accessibility_cost(t, r, dist=1).in_range(0, 0.5) ) ) - # * ( # allow a storage object behind non-wall sofas - # storage.related_to(r) - # .related_to(freestanding(sofas, r)) - # .count().in_range(0, 1) + # * ( # allow a storage object behind non-wall sofas + # storage.related_to(r, cu.on_floor) + # .related_to(freestanding(sofas, r), cu.back_to_back) + # .count() + # .in_range(0, 1) # ) ) ) @@ -1150,7 +1167,7 @@ def freestanding(o, r): lambda s: ( (cl.accessibility_cost(s, rooms, dist=3) < 0.5) * ( - cl.focus_score(s, tvstands.related_to(r)) > 0.5 + cl.focus_score(s, tvstands.related_to(r)) < 0.5 ) # must face or perpendicular to TVStand ) ) @@ -1162,13 +1179,12 @@ def freestanding(o, r): sofas.volume().maximize(weight=10) + sofas.related_to(r).mean( lambda t: ( - t.distance(sofas.related_to(r)).hinge(0, 1).minimize(weight=1) + t.distance(sofas.related_to(r)).hinge(0, 1).minimize(weight=5) + t.distance(tvstands.related_to(r)).hinge(2, 3).minimize(weight=5) - + cl.focus_score(t, tvstands.related_to(r)).maximize(weight=5) + + cl.focus_score(t, tvstands.related_to(r)).minimize(weight=5) + cl.angle_alignment_cost( t, tvstands.related_to(r), cu.front ).minimize(weight=1) - + cl.focus_score(t, coffeetables.related_to(r)).maximize(weight=2) + cl.accessibility_cost(t, r, dist=3).minimize(weight=3) ) ) @@ -1193,8 +1209,8 @@ def freestanding(o, r): lambda t: ( (tvs.related_to(t).count() == 1) * tvs.related_to(t).all( - lambda tv: cl.accessibility_cost(tv, r, dist=1).in_range( - 0, 0.1 + lambda tv: ( + cl.accessibility_cost(tv, r, dist=1).in_range(0, 0.1) ) ) ) @@ -1224,14 +1240,9 @@ def freestanding(o, r): constraints["livingroom"] = livingrooms.all( lambda r: ( - storage.related_to(r).count().in_range(1, 5) + storage_freestanding.related_to(r).count().in_range(0, 5) * tvstands.related_to(r).count().equals(1) - * ( # allow sidetables next to any sofa - sidetable.related_to(r) - .related_to(sofas.related_to(r), cu.side_by_side) - .count() - .in_range(0, 2) - ) + * sidetables.related_to(sofas.related_to(r)).count().in_range(0, 2) * desks.related_to(r).count().in_range(0, 1) * coffeetables.related_to(r).count().in_range(0, 1) * coffeetables.related_to(r).all( @@ -1260,7 +1271,7 @@ def freestanding(o, r): + cl.angle_alignment_cost( t, sofas.related_to(r), cu.front ).minimize(weight=5) - + cl.focus_score(sofas.related_to(r), t).maximize(weight=5) + + cl.focus_score(sofas.related_to(r), t).minimize(weight=5) ) ) ) @@ -1377,10 +1388,13 @@ def freestanding(o, r): # region BATHROOMS bathrooms = rooms[Semantics.Bathroom].excludes(cu.room_types) + toilet = wallfurn[bathroom.ToiletFactory] bathtub = wallfurn[bathroom.BathtubFactory] sink = wallfurn[bathroom.StandingSinkFactory] + hardware = obj[bathroom.HardwareFactory].related_to(bathrooms, cu.against_wall) + constraints["bathroom"] = bathrooms.all( lambda r: ( mirror.related_to(r).related_to(r, cu.flush_wall).count().equals(1) @@ -1421,8 +1435,8 @@ def freestanding(o, r): ) ) - score_terms["bathroom"] = mirror.related_to(bathrooms).distance(sink).minimize( - weight=0.2 + score_terms["bathroom"] = ( + mirror.related_to(bathrooms).distance(sink).minimize(weight=3) ) + cl.accessibility_cost(mirror, furniture, cu.down_dir).maximize(weight=3) # endregion diff --git a/infinigen_examples/constraints/util.py b/infinigen_examples/constraints/util.py index ac11e14aa..7d27df08d 100644 --- a/infinigen_examples/constraints/util.py +++ b/infinigen_examples/constraints/util.py @@ -72,9 +72,8 @@ ontop = cl.StableAgainst(bottom, top) on = cl.StableAgainst(bottom, {t.Subpart.SupportSurface}) -front_against = cl.StableAgainst( - front, side, margin=0.05, check_z=False -) # check_z=False +front_against = cl.StableAgainst(front, side, margin=0.05, check_z=False) +front_to_front = cl.StableAgainst(front, front, margin=0.05, check_z=False) leftright_leftright = cl.StableAgainst(leftright, leftright, margin=0.05) side_by_side = cl.StableAgainst(side, side) back_to_back = cl.StableAgainst(back, back) diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index 115b5d210..94daa44de 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -87,7 +87,12 @@ def default_greedy_stages(): greedy_stages["rooms"] = all_room - greedy_stages["on_floor"] = primary.with_relation(on_floor, all_room) + greedy_stages["on_floor_and_wall"] = primary.with_relation( + on_floor, all_room + ).with_relation(on_wall, all_room) + greedy_stages["on_floor_freestanding"] = primary.with_relation( + on_floor, all_room + ).with_relation(-on_wall, all_room) greedy_stages["on_wall"] = ( primary.with_relation(-on_floor, all_room) .with_relation(-on_ceiling, all_room) @@ -100,18 +105,25 @@ def default_greedy_stages(): ) secondary = all_obj.with_relation( - cl.AnyRelation(), primary.with_tags(cu.variable_obj) + cl.AnyRelation(), all_obj_in_room.with_tags(cu.variable_obj) ) - greedy_stages["side_obj"] = secondary.with_relation(side, all_obj) - nonside = secondary.with_relation(-side, all_obj) + greedy_stages["side_obj"] = ( + secondary.with_relation(side, all_obj) + .with_relation(-cu.on, all_obj) + .with_relation(-cu.ontop, all_obj) + ) - greedy_stages["obj_ontop_obj"] = nonside.with_relation( - cu.ontop, all_obj - ).with_relation(-cu.on, all_obj) - greedy_stages["obj_on_support"] = nonside.with_relation( - cu.on, all_obj - ).with_relation(-cu.ontop, all_obj) + greedy_stages["obj_ontop_obj"] = ( + secondary.with_relation(-side, all_obj) + .with_relation(cu.ontop, all_obj) + .with_relation(-cu.on, all_obj) + ) + greedy_stages["obj_on_support"] = ( + secondary.with_relation(-side, all_obj) + .with_relation(cu.on, all_obj) + .with_relation(-cu.ontop, all_obj) + ) return greedy_stages @@ -176,27 +188,31 @@ def solve_rooms(): state: state_def.State = p.run_stage("solve_rooms", solve_rooms, use_chance=False) - def solve_large(): - assignments = greedy.iterate_assignments( - stages["on_floor"], state, all_vars, limits, nonempty=True + def solve_stage_name(stage_name: str, group: str, **kwargs): + assigments = greedy.iterate_assignments( + stages[stage_name], state, all_vars, limits ) - for i, vars in enumerate(assignments): + for i, vars in enumerate(assigments): solver.solve_objects( consgraph, - stages["on_floor"], - var_assignments=vars, - n_steps=overrides["solve_steps_large"], - desc=f"on_floor_{i}", - abort_unsatisfied=overrides.get("abort_unsatisfied_large", False), + stages[stage_name], + vars, + n_steps=overrides[f"solve_steps_{group}"], + desc=f"{stage_name}_{i}", + abort_unsatisfied=overrides.get(f"abort_unsatisfied_{group}", False), + **kwargs, ) - return solver.state - state = p.run_stage("solve_large", solve_large, use_chance=False, default=state) + def solve_large(): + solve_stage_name("on_floor_and_wall", "large") + solve_stage_name("on_floor_freestanding", "large") + + p.run_stage("solve_large", solve_large, use_chance=False, default=state) solved_rooms = [ state.objs[assignment[cu.variable_room]].obj for assignment in greedy.iterate_assignments( - stages["on_floor"], state, [cu.variable_room], limits + stages["on_floor_freestanding"], state, [cu.variable_room], limits ) ] solved_bound_points = np.concatenate([butil.bounds(r) for r in solved_rooms]) @@ -263,58 +279,17 @@ def animate_cameras(): ) def solve_medium(): - n_steps = overrides["solve_steps_medium"] - for i, vars in enumerate( - greedy.iterate_assignments(stages["on_wall"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, stages["on_wall"], vars, n_steps, desc=f"on_wall_{i}" - ) - for i, vars in enumerate( - greedy.iterate_assignments(stages["on_ceiling"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, stages["on_ceiling"], vars, n_steps, desc=f"on_ceiling_{i}" - ) - for i, vars in enumerate( - greedy.iterate_assignments(stages["side_obj"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, stages["side_obj"], vars, n_steps, desc=f"side_obj_{i}" - ) - return solver.state + solve_stage_name("on_wall", "medium") + solve_stage_name("on_ceiling", "medium") + solve_stage_name("side_obj", "medium") - state = p.run_stage("solve_medium", solve_medium, use_chance=False, default=state) + p.run_stage("solve_medium", solve_medium, use_chance=False, default=state) def solve_small(): - n_steps = overrides["solve_steps_small"] - for i, vars in enumerate( - greedy.iterate_assignments(stages["obj_ontop_obj"], state, all_vars, limits) - ): - solver.solve_objects( - consgraph, - stages["obj_ontop_obj"], - vars, - n_steps, - desc=f"obj_ontop_obj_{i}", - ) - for i, vars in enumerate( - greedy.iterate_assignments( - stages["obj_on_support"], state, all_vars, limits - ) - ): - solver.solve_objects( - consgraph, - stages["obj_on_support"], - vars, - n_steps, - desc=f"obj_on_support_{i}", - ) - # for i, vars in enumerate(greedy.iterate_assignments(stages['tertiary'], state, all_vars, limits)): - # solver.solve_objects(consgraph, stages['tertiary'], vars, n_steps, desc=f"tertiary_{i}") - return solver.state + solve_stage_name("obj_ontop_obj", "small", addition_weight_scalar=3) + solve_stage_name("obj_on_support", "small", restrict_moves=["addition"]) - state = p.run_stage("solve_small", solve_small, use_chance=False, default=state) + p.run_stage("solve_small", solve_small, use_chance=False, default=state) p.run_stage( "populate_assets", populate.populate_state_placeholders, state, use_chance=False diff --git a/pyproject.toml b/pyproject.toml index 8f355d1eb..98935e94f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,8 +129,11 @@ test-command = "pytest tests" target-version = "py310" exclude = [ + + "*.ipynb", + # exclude known submodules - "*/dependencies/", + "infinigen/datagen/customgt/dependencies/", "infinigen/OcMesher", "infinigen/infinigen_gpl", diff --git a/tests/solver/test_constraint_evaluator.py b/tests/solver/test_constraint_evaluator.py index 5b22d21e8..4ca07bd03 100644 --- a/tests/solver/test_constraint_evaluator.py +++ b/tests/solver/test_constraint_evaluator.py @@ -609,6 +609,34 @@ def mk_state(n): assert evaluate.evaluate_problem(cons, mk_state(6))[1] == 3 +def test_viol_integers(): + a = cl.constant(1) + b = cl.constant(3) + + def violsingle(expr): + return evaluate.evaluate_problem(cl.Problem([expr], []), State()).viol_count() + + assert violsingle(a < b) == 0 + assert violsingle(b > a) == 0 + assert violsingle(a <= b) == 0 + assert violsingle(b >= a) == 0 + + assert violsingle(b <= b) == 0 + assert violsingle(b >= b) == 0 + + assert violsingle(b < b) == 1 + assert violsingle(b > b) == 1 + + assert violsingle(a == b) == 2 + assert violsingle(b <= a) == 2 + assert violsingle(a >= b) == 2 + + assert violsingle(b < a) == 3 + assert violsingle(a > b) == 3 + + assert violsingle(a >= (b * 2)) == 5 + + def test_min_dist_tagged(): butil.clear_scene() obj_states = {} diff --git a/tests/solver/test_greedy_partition.py b/tests/solver/test_greedy_partition.py index 910d822f7..fa6345b97 100644 --- a/tests/solver/test_greedy_partition.py +++ b/tests/solver/test_greedy_partition.py @@ -86,7 +86,7 @@ def test_greedy_partition_bathroom(): bath_cons = prob.constraints["bathroom"] - on_floor = stages["on_floor"] + on_floor = stages["on_floor_and_wall"] on_floor_any = r.domain_tag_substitute(on_floor, cu.variable_room, r.Domain()) assert greedy.filter_constraints(bath_cons, on_floor_any)[1] @@ -108,7 +108,7 @@ def test_greedy_partition_multilevel(): bath_cons_1 = storage.related_to(bathroom, cu.on_floor).count().in_range(0, 1) on_hallway = r.domain_tag_substitute( - stages["on_floor"], cu.variable_room, r.Domain({t.Semantics.Hallway}) + stages["on_floor_and_wall"], cu.variable_room, r.Domain({t.Semantics.Hallway}) ) assert not greedy.filter_constraints(bath_cons_1, on_hallway)[1] @@ -138,7 +138,7 @@ def test_greedy_partition_bathroom_nofalsepositive(): bath_cons = prob.constraints["bathroom"] on_hallway = r.domain_tag_substitute( - stages["on_floor"], cu.variable_room, r.Domain({t.Semantics.Hallway}) + stages["on_floor_and_wall"], cu.variable_room, r.Domain({t.Semantics.Hallway}) ) assert not greedy.filter_constraints(bath_cons, on_hallway)[1] @@ -150,7 +150,7 @@ def test_greedy_partition_plants(): plant_cons = prob.constraints["plants"] - on_floor = stages["on_floor"] + on_floor = stages["on_floor_and_wall"] on_floor_any = r.domain_tag_substitute(on_floor, cu.variable_room, r.Domain()) assert greedy.filter_constraints(plant_cons, on_floor_any)[1] @@ -224,7 +224,7 @@ def test_only_bathcons_coverage(): bath_cons = prob.constraints["bathroom"] dom = r.domain_tag_substitute( - stages["on_floor"], cu.variable_room, r.Domain({t.Semantics.Bathroom}) + stages["on_floor_and_wall"], cu.variable_room, r.Domain({t.Semantics.Bathroom}) ) assert greedy.filter_constraints(bath_cons, dom)[1] @@ -274,19 +274,19 @@ def test_specific_coverage(precompute_all_coverage): cons_coverage, _ = precompute_all_coverage assert cons_coverage["bathroom"] == { - ("on_floor", t.Semantics.Bathroom), + ("on_floor_and_wall", t.Semantics.Bathroom), ("on_wall", t.Semantics.Bathroom), ("on_obj", t.Semantics.Bathroom), } assert cons_coverage["diningroom"] == { - ("on_floor", t.Semantics.DiningRoom), + ("on_floor_and_wall", t.Semantics.DiningRoom), ("on_wall", t.Semantics.DiningRoom), ("on_obj", t.Semantics.DiningRoom), } assert cons_coverage["livingroom"] == { - ("on_floor", t.Semantics.LivingRoom), + ("on_floor_and_wall", t.Semantics.LivingRoom), ("on_wall", t.Semantics.LivingRoom), ("on_obj", t.Semantics.LivingRoom), } @@ -308,7 +308,7 @@ def get_on_diningroom_stage(): usage_lookup.initialize_from_dict(ex.home_asset_usage()) stages = generate_indoors.default_greedy_stages() on_diningroom = r.domain_tag_substitute( - stages["on_floor"], + stages["on_floor_and_wall"], cu.variable_room, r.Domain({t.Semantics.DiningRoom, t.Semantics.Room}), ) @@ -340,7 +340,9 @@ def test_diningroom_bounds_active(): usage_lookup.initialize_from_dict(ex.home_asset_usage()) stages = generate_indoors.default_greedy_stages() on_diningroom = r.domain_tag_substitute( - stages["on_floor"], cu.variable_room, r.Domain({t.Semantics.DiningRoom}) + stages["on_floor_freestanding"], + cu.variable_room, + r.Domain({t.Semantics.DiningRoom}), ) prob = ex.home_furniture_constraints() diff --git a/tests/solver/test_greedy_stages.py b/tests/solver/test_greedy_stages.py index abe917267..38317f6ef 100644 --- a/tests/solver/test_greedy_stages.py +++ b/tests/solver/test_greedy_stages.py @@ -71,7 +71,7 @@ def test_validate_stages(): stages = generate_indoors.default_greedy_stages() wall = stages["on_wall"] - floor = stages["on_floor"] + floor = stages["on_floor_freestanding"] assert not wall.intersects(floor) onobj = stages["obj_ontop_obj"] @@ -138,7 +138,7 @@ def test_example_walldec(): stages = generate_indoors.default_greedy_stages() assert not propose_discrete.active_for_stage(dom, stages["on_ceiling"]) - assert not propose_discrete.active_for_stage(dom, stages["on_floor"]) + assert not propose_discrete.active_for_stage(dom, stages["on_floor_freestanding"]) assert t.satisfies(dom.tags, stages["on_wall"].tags) print("ONWALL", stages["on_wall"]) @@ -182,7 +182,7 @@ def test_example_floorwall(): stages = generate_indoors.default_greedy_stages() - assert propose_discrete.active_for_stage(dom, stages["on_floor"]) + assert propose_discrete.active_for_stage(dom, stages["on_floor_and_wall"]) assert not propose_discrete.active_for_stage(dom, stages["on_wall"]) @@ -218,7 +218,7 @@ def test_example_sideobj(): ) stages = generate_indoors.default_greedy_stages() assert propose_discrete.active_for_stage(dom, stages["side_obj"]) - assert not propose_discrete.active_for_stage(dom, stages["on_floor"]) + assert not propose_discrete.active_for_stage(dom, stages["on_floor_freestanding"]) def test_example_monitor(): @@ -275,7 +275,7 @@ def test_example_on_obj(): {t.Semantics.OfficeShelfItem, t.Semantics.Object}, [(cu.on, bedroom_storage)] ) - onfloor = generate_indoors.default_greedy_stages()["on_floor"] + onfloor = generate_indoors.default_greedy_stages()["on_floor_freestanding"] dining = r.domain_tag_substitute( onfloor, cu.variable_room, r.Domain({t.Semantics.DiningRoom}) ) @@ -284,7 +284,7 @@ def test_example_on_obj(): def test_active_incorrect_room(): - onfloor = generate_indoors.default_greedy_stages()["on_floor"] + onfloor = generate_indoors.default_greedy_stages()["on_floor_freestanding"] dining = r.domain_tag_substitute( onfloor, cu.variable_room, r.Domain({t.Semantics.DiningRoom}) ) @@ -309,28 +309,6 @@ def test_active_incorrect_room(): assert not propose_discrete.active_for_stage(sofa, dining) -def test_stage_intersect_table(): - onfloor = generate_indoors.default_greedy_stages()["on_floor"] - onfloor_dining = r.domain_tag_substitute( - onfloor, - cu.variable_room, - r.Domain({t.Semantics.DiningRoom, t.SpecificObject("diningroom01")}), - ) - - dining = r.Domain( - {t.Semantics.Room, t.Semantics.DiningRoom, -t.Semantics.Object}, [] - ) - table = r.Domain( - {t.Semantics.Object, t.Semantics.Table, -t.Semantics.Room}, - [(cu.on_floor, dining)], - ) - - inter = onfloor_dining.intersection(table) - assert len(inter.relations) == 2 - assert inter.relations[0][0].__class__ is cl.StableAgainst - assert inter.relations[1][0].__class__ is cl.NegatedRelation - - def test_obj_on_ceilinglight(): bounds = r.constraint_bounds(ex.home_furniture_constraints()) @@ -377,7 +355,7 @@ def test_room_has_viols_at_init(rtype): print("active", rtype, active_count) assert active_count > 0 - filter = generate_indoors.default_greedy_stages()["on_floor"] + filter = generate_indoors.default_greedy_stages()["on_floor_freestanding"] filter = r.domain_tag_substitute( filter, cu.variable_room, r.Domain({rtype, t.Semantics.Room}) )